diff --git a/crates/handlers/src/admin/mod.rs b/crates/handlers/src/admin/mod.rs index 3b041ed10..f0187e246 100644 --- a/crates/handlers/src/admin/mod.rs +++ b/crates/handlers/src/admin/mod.rs @@ -81,35 +81,61 @@ fn finish(t: TransformOpenApi) -> TransformOpenApi { ), ..Default::default() }) + .security_scheme("oauth2", oauth_security_scheme(None)) .security_scheme( - "oauth2", - SecurityScheme::OAuth2 { - flows: OAuth2Flows { - client_credentials: Some(OAuth2Flow::ClientCredentials { - refresh_url: Some(OAuth2TokenEndpoint::PATH.to_owned()), - token_url: OAuth2TokenEndpoint::PATH.to_owned(), - scopes: IndexMap::from([( - "urn:mas:admin".to_owned(), - "Grant access to the admin API".to_owned(), - )]), - }), - authorization_code: Some(OAuth2Flow::AuthorizationCode { - authorization_url: OAuth2AuthorizationEndpoint::PATH.to_owned(), - refresh_url: Some(OAuth2TokenEndpoint::PATH.to_owned()), - token_url: OAuth2TokenEndpoint::PATH.to_owned(), - scopes: IndexMap::from([( - "urn:mas:admin".to_owned(), - "Grant access to the admin API".to_owned(), - )]), - }), - implicit: None, - password: None, - }, - description: None, + "token", + SecurityScheme::Http { + scheme: "bearer".to_owned(), + bearer_format: None, + description: Some("An access token with access to the admin API".to_owned()), extensions: IndexMap::default(), }, ) .security_requirement_scopes("oauth2", ["urn:mas:admin"]) + .security_requirement_scopes("bearer", ["urn:mas:admin"]) +} + +fn oauth_security_scheme(url_builder: Option<&UrlBuilder>) -> SecurityScheme { + let (authorization_url, token_url) = if let Some(url_builder) = url_builder { + ( + url_builder.oauth_authorization_endpoint().to_string(), + url_builder.oauth_token_endpoint().to_string(), + ) + } else { + // This is a dirty fix for Swagger UI: when it joins the URLs with the + // base URL, if the path starts with a slash, it will go to the root of + // the domain instead of the API root. + // It works if we make it explicitly relative + ( + format!(".{}", OAuth2AuthorizationEndpoint::PATH), + format!(".{}", OAuth2TokenEndpoint::PATH), + ) + }; + + let scopes = IndexMap::from([( + "urn:mas:admin".to_owned(), + "Grant access to the admin API".to_owned(), + )]); + + SecurityScheme::OAuth2 { + flows: OAuth2Flows { + client_credentials: Some(OAuth2Flow::ClientCredentials { + refresh_url: Some(token_url.clone()), + token_url: token_url.clone(), + scopes: scopes.clone(), + }), + authorization_code: Some(OAuth2Flow::AuthorizationCode { + authorization_url, + refresh_url: Some(token_url.clone()), + token_url, + scopes, + }), + implicit: None, + password: None, + }, + description: None, + extensions: IndexMap::default(), + } } pub fn router() -> (OpenApi, Router) @@ -146,10 +172,13 @@ where move |State(url_builder): State| { // Let's set the servers to the HTTP base URL let mut api = api.clone(); - api.servers = vec![Server { - url: url_builder.http_base().to_string(), - ..Server::default() - }]; + + let _ = TransformOpenApi::new(&mut api) + .server(Server { + url: url_builder.http_base().to_string(), + ..Server::default() + }) + .security_scheme("oauth2", oauth_security_scheme(Some(&url_builder))); std::future::ready(Json(api)) } diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 5b8a5e48a..36028cb3e 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -249,6 +249,8 @@ where ACCEPT_LANGUAGE, CONTENT_LANGUAGE, CONTENT_TYPE, + // Swagger will send this header, so we have to allow it to avoid CORS errors + HeaderName::from_static("x-requested-with"), ]) .max_age(Duration::from_secs(60 * 60)), ) diff --git a/docs/api/spec.json b/docs/api/spec.json index 1c7be2995..5b479b942 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -2527,21 +2527,26 @@ "type": "oauth2", "flows": { "clientCredentials": { - "refreshUrl": "/oauth2/token", - "tokenUrl": "/oauth2/token", + "refreshUrl": "./oauth2/token", + "tokenUrl": "./oauth2/token", "scopes": { "urn:mas:admin": "Grant access to the admin API" } }, "authorizationCode": { - "authorizationUrl": "/authorize", - "tokenUrl": "/oauth2/token", - "refreshUrl": "/oauth2/token", + "authorizationUrl": "./authorize", + "tokenUrl": "./oauth2/token", + "refreshUrl": "./oauth2/token", "scopes": { "urn:mas:admin": "Grant access to the admin API" } } } + }, + "token": { + "type": "http", + "scheme": "bearer", + "description": "An access token with access to the admin API" } }, "schemas": { @@ -3725,6 +3730,11 @@ "oauth2": [ "urn:mas:admin" ] + }, + { + "bearer": [ + "urn:mas:admin" + ] } ], "tags": [