Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 68 additions & 59 deletions crates/handlers/src/admin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use aide::{
axum::ApiRouter,
openapi::{OAuth2Flow, OAuth2Flows, OpenApi, SecurityScheme, Server, Tag},
transform::TransformOpenApi,
};
use axum::{
extract::{FromRef, FromRequestParts, State},
Expand Down Expand Up @@ -37,6 +38,72 @@ mod v1;
use self::call_context::CallContext;
use crate::passwords::PasswordManager;

fn finish(t: TransformOpenApi) -> TransformOpenApi {
t.title("Matrix Authentication Service admin API")
.tag(Tag {
name: "compat-session".to_owned(),
description: Some("Manage compatibility sessions from legacy clients".to_owned()),
..Tag::default()
})
.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()
})
.tag(Tag {
name: "user-email".to_owned(),
description: Some("Manage emails associated with users".to_owned()),
..Tag::default()
})
.tag(Tag {
name: "user-session".to_owned(),
description: Some("Manage browser sessions of users".to_owned()),
..Tag::default()
})
.tag(Tag {
name: "upstream-oauth-link".to_owned(),
description: Some(
"Manage links between local users and identities from upstream OAuth 2.0 providers"
.to_owned(),
),
..Default::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"])
}

pub fn router<S>() -> (OpenApi, Router<S>)
where
S: Clone + Send + Sync + 'static,
Expand All @@ -58,65 +125,7 @@ where
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: "compat-session".to_owned(),
description: Some(
"Manage compatibility sessions from legacy clients".to_owned(),
),
..Tag::default()
})
.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()
})
.tag(Tag {
name: "user-email".to_owned(),
description: Some("Manage emails associated with users".to_owned()),
..Tag::default()
})
.tag(Tag {
name: "user-session".to_owned(),
description: Some("Manage browser sessions of 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"])
});
.finish_api_with(&mut api, finish);

let router = router
// Serve the OpenAPI spec as JSON
Expand Down
78 changes: 78 additions & 0 deletions crates/handlers/src/admin/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,81 @@ impl Resource for UserSession {
self.id
}
}

/// An upstream OAuth 2.0 link
#[derive(Serialize, JsonSchema)]
pub struct UpstreamOAuthLink {
#[serde(skip)]
id: Ulid,

/// When the object was created
created_at: DateTime<Utc>,

/// The ID of the provider
#[schemars(with = "super::schema::Ulid")]
provider_id: Ulid,

/// The subject of the upstream account, unique per provider
subject: String,

/// The ID of the user who owns this link, if any
#[schemars(with = "Option<super::schema::Ulid>")]
user_id: Option<Ulid>,

/// A human-readable name of the upstream account
human_account_name: Option<String>,
}

impl Resource for UpstreamOAuthLink {
const KIND: &'static str = "upstream-oauth-link";
const PATH: &'static str = "/api/admin/v1/upstream-oauth-links";

fn id(&self) -> Ulid {
self.id
}
}

impl From<mas_data_model::UpstreamOAuthLink> for UpstreamOAuthLink {
fn from(value: mas_data_model::UpstreamOAuthLink) -> Self {
Self {
id: value.id,
created_at: value.created_at,
provider_id: value.provider_id,
subject: value.subject,
user_id: value.user_id,
human_account_name: value.human_account_name,
}
}
}

impl UpstreamOAuthLink {
/// Samples of upstream OAuth 2.0 links
pub fn samples() -> [Self; 3] {
[
Self {
id: Ulid::from_bytes([0x01; 16]),
created_at: DateTime::default(),
provider_id: Ulid::from_bytes([0x02; 16]),
subject: "john-42".to_owned(),
user_id: Some(Ulid::from_bytes([0x03; 16])),
human_account_name: Some("[email protected]".to_owned()),
},
Self {
id: Ulid::from_bytes([0x02; 16]),
created_at: DateTime::default(),
provider_id: Ulid::from_bytes([0x03; 16]),
subject: "jane-123".to_owned(),
user_id: None,
human_account_name: None,
},
Self {
id: Ulid::from_bytes([0x03; 16]),
created_at: DateTime::default(),
provider_id: Ulid::from_bytes([0x04; 16]),
subject: "[email protected]".to_owned(),
user_id: Some(Ulid::from_bytes([0x05; 16])),
human_account_name: Some("bob".to_owned()),
},
]
}
}
15 changes: 15 additions & 0 deletions crates/handlers/src/admin/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::passwords::PasswordManager;

mod compat_sessions;
mod oauth2_sessions;
mod upstream_oauth_links;
mod user_emails;
mod user_sessions;
mod users;
Expand Down Expand Up @@ -95,4 +96,18 @@ where
"/user-sessions/{id}",
get_with(self::user_sessions::get, self::user_sessions::get_doc),
)
.api_route(
"/upstream-oauth-links",
get_with(
self::upstream_oauth_links::list,
self::upstream_oauth_links::list_doc,
),
)
.api_route(
"/upstream-oauth-links/{id}",
get_with(
self::upstream_oauth_links::get,
self::upstream_oauth_links::get_doc,
),
)
}
Loading
Loading