diff --git a/crates/handlers/src/admin/mod.rs b/crates/handlers/src/admin/mod.rs index 8cc2956c0..2670d35ab 100644 --- a/crates/handlers/src/admin/mod.rs +++ b/crates/handlers/src/admin/mod.rs @@ -20,7 +20,7 @@ use axum::{ use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}; use indexmap::IndexMap; use mas_axum_utils::InternalError; -use mas_data_model::BoxRng; +use mas_data_model::{BoxRng, SiteConfig}; use mas_http::CorsLayerExt; use mas_matrix::HomeserverConnection; use mas_policy::PolicyFactory; @@ -43,6 +43,11 @@ use crate::passwords::PasswordManager; fn finish(t: TransformOpenApi) -> TransformOpenApi { t.title("Matrix Authentication Service admin API") + .tag(Tag { + name: "server".to_owned(), + description: Some("Information about the server".to_owned()), + ..Tag::default() + }) .tag(Tag { name: "compat-session".to_owned(), description: Some("Manage compatibility sessions from legacy clients".to_owned()), @@ -153,6 +158,7 @@ where Templates: FromRef, UrlBuilder: FromRef, Arc: FromRef, + SiteConfig: FromRef, { // We *always* want to explicitly set the possible responses, beacuse the // infered ones are not necessarily correct diff --git a/crates/handlers/src/admin/v1/mod.rs b/crates/handlers/src/admin/v1/mod.rs index 49ff75001..afe71a05f 100644 --- a/crates/handlers/src/admin/v1/mod.rs +++ b/crates/handlers/src/admin/v1/mod.rs @@ -11,7 +11,7 @@ use aide::axum::{ routing::{get_with, post_with}, }; use axum::extract::{FromRef, FromRequestParts}; -use mas_data_model::BoxRng; +use mas_data_model::{BoxRng, SiteConfig}; use mas_matrix::HomeserverConnection; use mas_policy::PolicyFactory; @@ -21,6 +21,7 @@ use crate::passwords::PasswordManager; mod compat_sessions; mod oauth2_sessions; mod policy_data; +mod site_config; mod upstream_oauth_links; mod user_emails; mod user_registration_tokens; @@ -32,11 +33,16 @@ where S: Clone + Send + Sync + 'static, Arc: FromRef, PasswordManager: FromRef, + SiteConfig: FromRef, Arc: FromRef, BoxRng: FromRequestParts, CallContext: FromRequestParts, { ApiRouter::::new() + .api_route( + "/site-config", + get_with(self::site_config::handler, self::site_config::doc), + ) .api_route( "/compat-sessions", get_with(self::compat_sessions::list, self::compat_sessions::list_doc), diff --git a/crates/handlers/src/admin/v1/site_config.rs b/crates/handlers/src/admin/v1/site_config.rs new file mode 100644 index 000000000..b9b05dac7 --- /dev/null +++ b/crates/handlers/src/admin/v1/site_config.rs @@ -0,0 +1,92 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +use aide::transform::TransformOperation; +use axum::{Json, extract::State}; +use schemars::JsonSchema; +use serde::Serialize; + +use crate::admin::call_context::CallContext; + +#[allow(clippy::struct_excessive_bools)] +#[derive(Serialize, JsonSchema)] +pub struct SiteConfig { + /// The Matrix server name for which this instance is configured + server_name: String, + + /// Whether password login is enabled. + pub password_login_enabled: bool, + + /// Whether password registration is enabled. + pub password_registration_enabled: bool, + + /// Whether registration tokens are required for password registrations. + pub registration_token_required: bool, + + /// Whether users can change their email. + pub email_change_allowed: bool, + + /// Whether users can change their display name. + pub displayname_change_allowed: bool, + + /// Whether users can change their password. + pub password_change_allowed: bool, + + /// Whether users can recover their account via email. + pub account_recovery_allowed: bool, + + /// Whether users can delete their own account. + pub account_deactivation_allowed: bool, + + /// Whether CAPTCHA during registration is enabled. + pub captcha_enabled: bool, + + /// Minimum password complexity, between 0 and 4. + /// This is a score from zxcvbn. + #[schemars(range(min = 0, max = 4))] + pub minimum_password_complexity: u8, +} + +pub fn doc(operation: TransformOperation) -> TransformOperation { + operation + .id("siteConfig") + .tag("server") + .summary("Get informations about the configuration of this MAS instance") + .response_with::<200, Json, _>(|t| { + t.example(SiteConfig { + server_name: "example.com".to_owned(), + password_login_enabled: true, + password_registration_enabled: true, + registration_token_required: true, + email_change_allowed: true, + displayname_change_allowed: true, + password_change_allowed: true, + account_recovery_allowed: true, + account_deactivation_allowed: true, + captcha_enabled: true, + minimum_password_complexity: 3, + }) + }) +} + +#[tracing::instrument(name = "handler.admin.v1.site_config", skip_all)] +pub async fn handler( + _: CallContext, + State(site_config): State, +) -> Json { + Json(SiteConfig { + server_name: site_config.server_name, + password_login_enabled: site_config.password_login_enabled, + password_registration_enabled: site_config.password_registration_enabled, + registration_token_required: site_config.registration_token_required, + email_change_allowed: site_config.email_change_allowed, + displayname_change_allowed: site_config.displayname_change_allowed, + password_change_allowed: site_config.password_change_allowed, + account_recovery_allowed: site_config.account_recovery_allowed, + account_deactivation_allowed: site_config.account_deactivation_allowed, + captcha_enabled: site_config.captcha.is_some(), + minimum_password_complexity: site_config.minimum_password_complexity, + }) +} diff --git a/crates/handlers/src/bin/api-schema.rs b/crates/handlers/src/bin/api-schema.rs index 894546961..6eed219da 100644 --- a/crates/handlers/src/bin/api-schema.rs +++ b/crates/handlers/src/bin/api-schema.rs @@ -59,6 +59,7 @@ impl_from_ref!(Arc); impl_from_ref!(mas_keystore::Keystore); impl_from_ref!(mas_handlers::passwords::PasswordManager); impl_from_ref!(Arc); +impl_from_ref!(mas_data_model::SiteConfig); fn main() -> Result<(), Box> { let (mut api, _) = mas_handlers::admin_api_router::(); diff --git a/docs/api/spec.json b/docs/api/spec.json index 9e3e336aa..ab8e7f2ec 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -16,6 +16,40 @@ } ], "paths": { + "/api/admin/v1/site-config": { + "get": { + "tags": [ + "server" + ], + "summary": "Get informations about the configuration of this MAS instance", + "operationId": "siteConfig", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SiteConfig" + }, + "example": { + "server_name": "example.com", + "password_login_enabled": true, + "password_registration_enabled": true, + "registration_token_required": true, + "email_change_allowed": true, + "displayname_change_allowed": true, + "password_change_allowed": true, + "account_recovery_allowed": true, + "account_deactivation_allowed": true, + "captcha_enabled": true, + "minimum_password_complexity": 3 + } + } + } + } + } + } + }, "/api/admin/v1/compat-sessions": { "get": { "tags": [ @@ -3186,6 +3220,71 @@ } }, "schemas": { + "SiteConfig": { + "type": "object", + "required": [ + "account_deactivation_allowed", + "account_recovery_allowed", + "captcha_enabled", + "displayname_change_allowed", + "email_change_allowed", + "minimum_password_complexity", + "password_change_allowed", + "password_login_enabled", + "password_registration_enabled", + "registration_token_required", + "server_name" + ], + "properties": { + "server_name": { + "description": "The Matrix server name for which this instance is configured", + "type": "string" + }, + "password_login_enabled": { + "description": "Whether password login is enabled.", + "type": "boolean" + }, + "password_registration_enabled": { + "description": "Whether password registration is enabled.", + "type": "boolean" + }, + "registration_token_required": { + "description": "Whether registration tokens are required for password registrations.", + "type": "boolean" + }, + "email_change_allowed": { + "description": "Whether users can change their email.", + "type": "boolean" + }, + "displayname_change_allowed": { + "description": "Whether users can change their display name.", + "type": "boolean" + }, + "password_change_allowed": { + "description": "Whether users can change their password.", + "type": "boolean" + }, + "account_recovery_allowed": { + "description": "Whether users can recover their account via email.", + "type": "boolean" + }, + "account_deactivation_allowed": { + "description": "Whether users can delete their own account.", + "type": "boolean" + }, + "captcha_enabled": { + "description": "Whether CAPTCHA during registration is enabled.", + "type": "boolean" + }, + "minimum_password_complexity": { + "description": "Minimum password complexity, between 0 and 4. This is a score from zxcvbn.", + "type": "integer", + "format": "uint8", + "maximum": 4.0, + "minimum": 0.0 + } + } + }, "PaginationParams": { "type": "object", "properties": { @@ -4586,6 +4685,10 @@ } ], "tags": [ + { + "name": "server", + "description": "Information about the server" + }, { "name": "compat-session", "description": "Manage compatibility sessions from legacy clients"