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}