use aide::{
axum::ApiRouter,
openapi::{OAuth2Flow, OAuth2Flows, OpenApi, SecurityScheme, Server, Tag},
};
use axum::{
extract::{FromRef, FromRequestParts, State},
http::HeaderName,
response::Html,
Json, Router,
};
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use indexmap::IndexMap;
use mas_axum_utils::FancyError;
use mas_http::CorsLayerExt;
use mas_matrix::BoxHomeserverConnection;
use mas_router::{
ApiDoc, ApiDocCallback, OAuth2AuthorizationEndpoint, OAuth2TokenEndpoint, Route, SimpleRoute,
UrlBuilder,
};
use mas_storage::BoxRng;
use mas_templates::{ApiDocContext, Templates};
use tower_http::cors::{Any, CorsLayer};
mod call_context;
mod model;
mod params;
mod response;
mod schema;
mod v1;
use self::call_context::CallContext;
use crate::passwords::PasswordManager;
pub fn router<S>() -> (OpenApi, Router<S>)
where
S: Clone + Send + Sync + 'static,
BoxHomeserverConnection: FromRef<S>,
PasswordManager: FromRef<S>,
BoxRng: FromRequestParts<S>,
CallContext: FromRequestParts<S>,
Templates: FromRef<S>,
UrlBuilder: FromRef<S>,
{
aide::gen::in_context(|ctx| {
ctx.schema = schemars::gen::SchemaGenerator::new(schemars::gen::SchemaSettings::openapi3());
});
let mut api = OpenApi::default();
let router = ApiRouter::<S>::new()
.nest("/api/admin/v1", self::v1::router())
.finish_api_with(&mut api, |t| {
t.title("Matrix Authentication Service admin API")
.tag(Tag {
name: "oauth2-session".to_owned(),
description: Some("Manage OAuth2 sessions".to_owned()),
..Tag::default()
})
.tag(Tag {
name: "user".to_owned(),
description: Some("Manage users".to_owned()),
..Tag::default()
})
.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,
extensions: IndexMap::default(),
},
)
.security_requirement_scopes("oauth2", ["urn:mas:admin"])
});
let router = router
.route(
"/api/spec.json",
axum::routing::get({
let api = api.clone();
move |State(url_builder): State<UrlBuilder>| {
let mut api = api.clone();
api.servers = vec![Server {
url: url_builder.http_base().to_string(),
..Server::default()
}];
std::future::ready(Json(api))
}
}),
)
.route(ApiDoc::route(), axum::routing::get(swagger))
.route(
ApiDocCallback::route(),
axum::routing::get(swagger_callback),
)
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_otel_headers([
AUTHORIZATION,
ACCEPT,
CONTENT_TYPE,
HeaderName::from_static("x-requested-with"),
]),
);
(api, router)
}
async fn swagger(
State(url_builder): State<UrlBuilder>,
State(templates): State<Templates>,
) -> Result<Html<String>, FancyError> {
let ctx = ApiDocContext::from_url_builder(&url_builder);
let res = templates.render_swagger(&ctx)?;
Ok(Html(res))
}
async fn swagger_callback(
State(url_builder): State<UrlBuilder>,
State(templates): State<Templates>,
) -> Result<Html<String>, FancyError> {
let ctx = ApiDocContext::from_url_builder(&url_builder);
let res = templates.render_swagger_callback(&ctx)?;
Ok(Html(res))
}