utoipa_swagger_ui/
oauth.rs

1//! Implements Swagger UI [oauth configuration](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md) options.
2
3use std::collections::HashMap;
4
5use serde::Serialize;
6
7const END_MARKER: &str = "//</editor-fold>";
8
9/// Object used to alter Swagger UI oauth settings.
10///
11/// # Examples
12///
13/// ```rust
14/// # use utoipa_swagger_ui::oauth;
15/// let config = oauth::Config::new()
16///     .client_id("client-id")
17///     .use_pkce_with_authorization_code_grant(true);
18/// ```
19#[non_exhaustive]
20#[derive(Default, Clone, Serialize)]
21#[cfg_attr(feature = "debug", derive(Debug))]
22#[serde(rename_all = "camelCase")]
23pub struct Config {
24    /// oauth client_id the Swagger UI is using for auth flow.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub client_id: Option<String>,
27
28    /// oauth client_secret the Swagger UI is using for auth flow.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub client_secret: Option<String>,
31
32    /// oauth realm the Swagger UI is using for auth flow.
33    /// realm query parameter (for oauth1) added to authorizationUrl and tokenUrl.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub realm: Option<String>,
36
37    /// oauth app_name the Swagger UI is using for auth flow.
38    /// application name, displayed in authorization popup.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub app_name: Option<String>,
41
42    /// oauth scope_separator the Swagger UI is using for auth flow.
43    /// scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20).
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub scope_separator: Option<String>,
46
47    /// oauth scopes the Swagger UI is using for auth flow.
48    /// [`Vec<String>`] of initially selected oauth scopes, default is empty.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub scopes: Option<Vec<String>>,
51
52    /// oauth additional_query_string_params the Swagger UI is using for auth flow.
53    /// [`HashMap<String, String>`] of additional query parameters added to authorizationUrl and tokenUrl
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub additional_query_string_params: Option<HashMap<String, String>>,
56
57    /// oauth use_basic_authentication_with_access_code_grant the Swagger UI is using for auth flow.
58    /// Only activated for the accessCode flow. During the authorization_code request to the tokenUrl,
59    /// pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme
60    /// (Authorization header with Basic base64encode(client_id + client_secret)).
61    /// The default is false
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub use_basic_authentication_with_access_code_grant: Option<bool>,
64
65    /// oauth use_pkce_with_authorization_code_grant the Swagger UI is using for auth flow.
66    /// Only applies to authorizationCode flows. [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
67    /// brings enhanced security for OAuth public clients.
68    /// The default is false
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub use_pkce_with_authorization_code_grant: Option<bool>,
71}
72
73impl Config {
74    /// Create a new [`Config`] for oauth auth flow.
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// # use utoipa_swagger_ui::oauth;
80    /// let config = oauth::Config::new();
81    /// ```
82    pub fn new() -> Self {
83        Self {
84            ..Default::default()
85        }
86    }
87
88    /// Add client_id into [`Config`].
89    ///
90    /// Method takes one argument which exposes the client_id to the user.
91    ///
92    /// # Examples
93    ///
94    /// ```rust
95    /// # use utoipa_swagger_ui::oauth;
96    /// let config = oauth::Config::new()
97    ///     .client_id("client-id");
98    /// ```
99    pub fn client_id(mut self, client_id: &str) -> Self {
100        self.client_id = Some(String::from(client_id));
101
102        self
103    }
104
105    /// Add client_secret into [`Config`].
106    ///
107    /// Method takes one argument which exposes the client_secret to the user.
108    /// 🚨 Never use this parameter in your production environment.
109    /// It exposes crucial security information. This feature is intended for dev/test environments only. 🚨
110    ///
111    /// # Examples
112    ///
113    /// ```rust
114    /// # use utoipa_swagger_ui::oauth;
115    /// let config = oauth::Config::new()
116    ///     .client_secret("client-secret");
117    /// ```
118    pub fn client_secret(mut self, client_secret: &str) -> Self {
119        self.client_secret = Some(String::from(client_secret));
120
121        self
122    }
123
124    /// Add realm into [`Config`].
125    ///
126    /// Method takes one argument which exposes the realm to the user.
127    /// realm query parameter (for oauth1) added to authorizationUrl and tokenUrl.
128    ///
129    /// # Examples
130    ///
131    /// ```rust
132    /// # use utoipa_swagger_ui::oauth;
133    /// let config = oauth::Config::new()
134    ///     .realm("realm");
135    /// ```
136    pub fn realm(mut self, realm: &str) -> Self {
137        self.realm = Some(String::from(realm));
138
139        self
140    }
141
142    /// Add app_name into [`Config`].
143    ///
144    /// Method takes one argument which exposes the app_name to the user.
145    /// application name, displayed in authorization popup.
146    ///
147    /// # Examples
148    ///
149    /// ```rust
150    /// # use utoipa_swagger_ui::oauth;
151    /// let config = oauth::Config::new()
152    ///     .app_name("app-name");
153    /// ```
154    pub fn app_name(mut self, app_name: &str) -> Self {
155        self.app_name = Some(String::from(app_name));
156
157        self
158    }
159
160    /// Add scope_separator into [`Config`].
161    ///
162    /// Method takes one argument which exposes the scope_separator to the user.
163    /// scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20).
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// # use utoipa_swagger_ui::oauth;
169    /// let config = oauth::Config::new()
170    ///     .scope_separator(",");
171    /// ```
172    pub fn scope_separator(mut self, scope_separator: &str) -> Self {
173        self.scope_separator = Some(String::from(scope_separator));
174
175        self
176    }
177
178    /// Add scopes into [`Config`].
179    ///
180    /// Method takes one argument which exposes the scopes to the user.
181    /// [`Vec<String>`] of initially selected oauth scopes, default is empty.
182    ///
183    /// # Examples
184    ///
185    /// ```rust
186    /// # use utoipa_swagger_ui::oauth;
187    /// let config = oauth::Config::new()
188    ///     .scopes(vec![String::from("openid")]);
189    /// ```
190    pub fn scopes(mut self, scopes: Vec<String>) -> Self {
191        self.scopes = Some(scopes);
192
193        self
194    }
195
196    /// Add additional_query_string_params into [`Config`].
197    ///
198    /// Method takes one argument which exposes the additional_query_string_params to the user.
199    /// [`HashMap<String, String>`] of additional query parameters added to authorizationUrl and tokenUrl
200    ///
201    /// # Examples
202    ///
203    /// ```rust
204    /// # use utoipa_swagger_ui::oauth;
205    /// # use std::collections::HashMap;
206    /// let config = oauth::Config::new()
207    ///     .additional_query_string_params(HashMap::from([(String::from("a"), String::from("1"))]));
208    /// ```
209    pub fn additional_query_string_params(
210        mut self,
211        additional_query_string_params: HashMap<String, String>,
212    ) -> Self {
213        self.additional_query_string_params = Some(additional_query_string_params);
214
215        self
216    }
217
218    /// Add use_basic_authentication_with_access_code_grant into [`Config`].
219    ///
220    /// Method takes one argument which exposes the use_basic_authentication_with_access_code_grant to the user.
221    /// Only activated for the accessCode flow. During the authorization_code request to the tokenUrl,
222    /// pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme
223    /// (Authorization header with Basic base64encode(client_id + client_secret)).
224    /// The default is false
225    ///
226    /// # Examples
227    ///
228    /// ```rust
229    /// # use utoipa_swagger_ui::oauth;
230    /// let config = oauth::Config::new()
231    ///     .use_basic_authentication_with_access_code_grant(true);
232    /// ```
233    pub fn use_basic_authentication_with_access_code_grant(
234        mut self,
235        use_basic_authentication_with_access_code_grant: bool,
236    ) -> Self {
237        self.use_basic_authentication_with_access_code_grant =
238            Some(use_basic_authentication_with_access_code_grant);
239
240        self
241    }
242
243    /// Add use_pkce_with_authorization_code_grant into [`Config`].
244    ///
245    /// Method takes one argument which exposes the use_pkce_with_authorization_code_grant to the user.
246    /// Only applies to authorizationCode flows. [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
247    /// brings enhanced security for OAuth public clients.
248    /// The default is false
249    ///
250    /// # Examples
251    ///
252    /// ```rust
253    /// # use utoipa_swagger_ui::oauth;
254    /// let config = oauth::Config::new()
255    ///     .use_pkce_with_authorization_code_grant(true);
256    /// ```
257    pub fn use_pkce_with_authorization_code_grant(
258        mut self,
259        use_pkce_with_authorization_code_grant: bool,
260    ) -> Self {
261        self.use_pkce_with_authorization_code_grant = Some(use_pkce_with_authorization_code_grant);
262
263        self
264    }
265}
266
267pub(crate) fn format_swagger_config(config: &Config, file: String) -> serde_json::Result<String> {
268    let init_string = format!(
269        "{}\nui.initOAuth({});",
270        END_MARKER,
271        serde_json::to_string_pretty(config)?
272    );
273    Ok(file.replace(END_MARKER, &init_string))
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    const TEST_CONTENT: &str = r###""
281    //<editor-fold desc=\"Changeable Configuration Block\">
282    window.ui = SwaggerUIBundle({
283        {{urls}},
284        dom_id: '#swagger-ui',
285        deepLinking: true,
286        presets: [
287            SwaggerUIBundle.presets.apis,
288            SwaggerUIStandalonePreset
289        ],
290        plugins: [
291            SwaggerUIBundle.plugins.DownloadUrl
292        ],
293        layout: "StandaloneLayout"
294    });
295    //</editor-fold>
296    ""###;
297
298    #[test]
299    fn format_swagger_config_oauth() {
300        let config = Config {
301            client_id: Some(String::from("my-special-client")),
302            ..Default::default()
303        };
304        let file = super::format_swagger_config(&config, TEST_CONTENT.to_string()).unwrap();
305
306        let expected = r#"
307ui.initOAuth({
308  "clientId": "my-special-client"
309});"#;
310        assert!(
311            file.contains(expected),
312            "expected file to contain {expected}, was {file}"
313        )
314    }
315}