From 29e88691abe3d8ef56fc46ae37e3bb0f89953b27 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 18 Aug 2025 13:23:09 +0200 Subject: [PATCH 1/5] Update `mas-cli doctor` to suggest the stable MAS integration --- crates/cli/src/commands/doctor.rs | 72 ++++++++++--------------------- 1 file changed, 22 insertions(+), 50 deletions(-) diff --git a/crates/cli/src/commands/doctor.rs b/crates/cli/src/commands/doctor.rs index 999aa71ba..7e1b8ea8e 100644 --- a/crates/cli/src/commands/doctor.rs +++ b/crates/cli/src/commands/doctor.rs @@ -14,6 +14,7 @@ use std::process::ExitCode; use anyhow::Context; use clap::Parser; use figment::Figment; +use hyper::StatusCode; use mas_config::{ConfigurationSection, RootConfig}; use mas_http::RequestBuilderExt; use tracing::{error, info, info_span, warn}; @@ -99,17 +100,14 @@ Make sure that the MAS config contains: http: public_base: {issuer:?} - # Or, if the issuer is different from the public base: - issuer: {issuer:?} And in the Synapse config: - experimental_features: - msc3861: - enabled: true - # This must exactly match: - issuer: {issuer:?} - # ... + matrix_authentication_service: + enabled: true + # This must point to where MAS is reachable by Synapse + endpoint: {issuer:?} + # ... See {DOCS_BASE}/setup/homeserver.html "# @@ -128,11 +126,10 @@ Check the well-known document at "{well_known_uri}" Check the well-known document at "{well_known_uri}" Make sure Synapse has delegated auth enabled: - experimental_features: - msc3861: - enabled: true - issuer: {issuer:?} - # ... + matrix_authentication_service: + enabled: true + endpoint: {issuer:?} + # ... If it is not Synapse handling the well-known document, update it to include the following: @@ -278,62 +275,37 @@ Check that the homeserver is running."# Err(e) => error!( r#"❌ Can't reach the homeserver at "{whoami}". -Error details: {e} -"# - ), - } - - // Try to reach the admin API on an unauthorized endpoint - let server_version = hs_api.join("/_synapse/admin/v1/server_version")?; - let result = http_client.get(server_version.as_str()).send_traced().await; - match result { - Ok(response) => { - let status = response.status(); - if status.is_success() { - info!(r#"✅ The Synapse admin API is reachable at "{server_version}"."#); - } else { - error!( - r#"❌ A Synapse admin API endpoint at "{server_version}" replied with {status}. -Make sure MAS can reach the admin API, and that the homeserver is running. -"# - ); - } - } - Err(e) => error!( - r#"❌ Can't reach the Synapse admin API at "{server_version}". -Make sure MAS can reach the admin API, and that the homeserver is running. - Error details: {e} "# ), } // Try to reach an authenticated admin API endpoint - let background_updates = hs_api.join("/_synapse/admin/v1/background_updates/status")?; + let mas_api = hs_api.join("/_synapse/mas/is_localpart_available")?; let result = http_client - .get(background_updates.as_str()) + .get(mas_api.as_str()) .bearer_auth(&admin_token) .send_traced() .await; match result { Ok(response) => { let status = response.status(); - if status.is_success() { + // We're missing the localpart parameter, so expect a 400 + if status == StatusCode::BAD_REQUEST { info!( - r#"✅ The Synapse admin API is reachable with authentication at "{background_updates}"."# + r#"✅ The Synapse admin API is reachable with authentication at "{mas_api}"."# ); } else { error!( - r#"❌ A Synapse admin API endpoint at "{background_updates}" replied with {status}. + r#"❌ A Synapse admin API endpoint at "{mas_api}" replied with {status}. Make sure the homeserver is running, and that the MAS config has the correct `matrix.secret`. It should match the `admin_token` set in the Synapse config. - experimental_features: - msc3861: - enabled: true - issuer: {issuer} - # This must exactly match the secret in the MAS config: - admin_token: {admin_token:?} + matrix_authentication_service: + enabled: true + endpoint: {issuer:?} + # This must exactly match the secret in the MAS config: + secret: {admin_token:?} And in the MAS config: @@ -346,7 +318,7 @@ And in the MAS config: } } Err(e) => error!( - r#"❌ Can't reach the Synapse admin API at "{background_updates}". + r#"❌ Can't reach the Synapse admin API at "{mas_api}". Make sure the homeserver is running, and that the MAS config has the correct `matrix.secret`. Error details: {e} From d3e3a6fe14070521d1a837158f1a440b7742fc49 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 18 Aug 2025 13:38:35 +0200 Subject: [PATCH 2/5] Document Synapse integration with the stable feature --- docs/setup/homeserver.md | 82 +++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/docs/setup/homeserver.md b/docs/setup/homeserver.md index 614d4b190..66a65887f 100644 --- a/docs/setup/homeserver.md +++ b/docs/setup/homeserver.md @@ -1,74 +1,46 @@ # Homeserver configuration The `matrix-authentication-service` is designed to be run alongside a Matrix homeserver. -It currently only supports [Synapse](https://github.com/element-hq/synapse) through the experimental OAuth delegation feature. +It currently only supports [Synapse](https://github.com/element-hq/synapse) version 1.136.0 or later. The authentication service needs to be able to call the Synapse admin API to provision users through a shared secret, and Synapse needs to be able to call the service to verify access tokens using the OAuth 2.0 token introspection endpoint. -## Provision a client for the Homeserver to use - -In the [`clients`](../reference/configuration.md#clients) section of the configuration file, add a new client with the following properties: - - - `client_id`: a unique identifier for the client. It must be a valid [ULID](https://github.com/ulid/spec), and it happens that `0000000000000000000SYNAPSE` is a valid ULID. - - `client_auth_method`: set to `client_secret_basic`. Other methods are possible, but this is the easiest to set up. - - `client_secret`: a shared secret used for the homeserver to authenticate - -```yaml -clients: - - client_id: 0000000000000000000SYNAPSE - client_auth_method: client_secret_basic - client_secret: "SomeRandomSecret" -``` - -**Don't forget to sync the configuration file** with the database after adding the client, using the [`config sync`](../reference/cli/config.md#config-sync---prune---dry-run) command. - ## Configure the connection to the homeserver In the [`matrix`](../reference/configuration.md#matrix) section of the configuration file, add the following properties: + - `kind`: the type of homeserver to connect to, currently only `synapse` is supported - `homeserver`: corresponds to the `server_name` in the Synapse configuration file - - `secret`: a shared secret the service will use to call the homeserver admin API + - `secret`: a shared secret the service will use to call the homeserver MAS API - `endpoint`: the URL to which the homeserver is accessible from the service ```yaml matrix: - homeserver: localhost:8008 - secret: "AnotherRandomSecret" + kind: synapse + homeserver: example.com endpoint: "http://localhost:8008" + secret: "AVeryRandomSecretPleaseUseSomethingSecure" + # Alternatively, using a file: + #secret_path: /path/to/secret.txt ``` ## Configure the homeserver to delegate authentication to the service -Set up the delegated authentication feature in the Synapse configuration in the `experimental_features` section: +Set up the delegated authentication feature **in the Synapse configuration** in the `matrix_authentication_service` section: ```yaml -experimental_features: - msc3861: - enabled: true - - # Synapse will call `{issuer}/.well-known/openid-configuration` to get the OIDC configuration - issuer: http://localhost:8080/ - - # Matches the `client_id` in the auth service config - client_id: 0000000000000000000SYNAPSE - # Matches the `client_auth_method` in the auth service config - client_auth_method: client_secret_basic - # Matches the `client_secret` in the auth service config - client_secret: "SomeRandomSecret" - - # Matches the `matrix.secret` in the auth service config - admin_token: "AnotherRandomSecret" - - # URL to advertise to clients where users can self-manage their account - # Defaults to the URL advertised by MAS, e.g. `https://{public_mas_domain}/account/` - #account_management_url: "http://localhost:8080/account/" - - # URL which Synapse will use to introspect access tokens - # Defaults to the URL advertised by MAS, e.g. `https://{public_mas_domain}/oauth2/introspect` - # This is useful to override if Synapse has a way to call the auth service's - # introspection endpoint directly, skipping intermediate reverse proxies - #introspection_endpoint: "http://localhost:8080/oauth2/introspect" +matrix_authentication_service: + enabled: true + endpoint: http://localhost:8080/ + secret: "AVeryRandomSecretPleaseUseSomethingSecure" + # Alternatively, using a file: + #secret_file: /path/to/secret.txt ``` +The `endpoint` property should be set to the URL of the authentication service. +This can be an internal URL, to avoid unnecessary round-trips. + +The `secret` property must match in both the Synapse configuration and the Matrix Authentication Service configuration. + ## Set up the compatibility layer The service exposes a compatibility layer to allow legacy clients to authenticate using the service. @@ -81,3 +53,17 @@ The following Matrix Client-Server API endpoints need to be handled by the authe - [`/_matrix/client/*/refresh`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3refresh) See the [reverse proxy configuration](./reverse-proxy.md) guide for more information. + + +## Migrating from the experimental MSC3861 feature + +If you are migrating from the experimental MSC3861 feature in Synapse, you will need to migrate the `experimental_features.msc3861` section of the Synapse configuration to the `matrix_authentication_service` section. + +To do so, you need to: + + - Remove the `experimental_features.msc3861` section from the Synapse configuration + - Add the `matrix_authentication_service` section to the Synapse configuration with: + - `enabled: true` + - `endpoint` set to the URL of the authentication service + - `secret` set to the same secret as the `admin_token` that was set in the `msc3861` section + - Optionally, remove the client provisionned for Synapse in the `clients` section of the MAS configuration From 4f33544eb762bd4c3df8ff832b6155f4883a2a87 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 18 Aug 2025 13:44:10 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/cli/src/commands/doctor.rs | 5 ++++- docs/setup/homeserver.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/commands/doctor.rs b/crates/cli/src/commands/doctor.rs index 7e1b8ea8e..0e24d4d92 100644 --- a/crates/cli/src/commands/doctor.rs +++ b/crates/cli/src/commands/doctor.rs @@ -290,7 +290,10 @@ Error details: {e} match result { Ok(response) => { let status = response.status(); - // We're missing the localpart parameter, so expect a 400 + // We intentionally omit the required 'localpart' parameter in this request. + // If authentication is successful, Synapse returns a 400 Bad Request because of the missing parameter. + // If authentication fails, Synapse will return a 403 Forbidden. + // If the MAS integration isn't enabled, Synapse will return a 404 Not found. if status == StatusCode::BAD_REQUEST { info!( r#"✅ The Synapse admin API is reachable with authentication at "{mas_api}"."# diff --git a/docs/setup/homeserver.md b/docs/setup/homeserver.md index 66a65887f..ead3f8a17 100644 --- a/docs/setup/homeserver.md +++ b/docs/setup/homeserver.md @@ -66,4 +66,4 @@ To do so, you need to: - `enabled: true` - `endpoint` set to the URL of the authentication service - `secret` set to the same secret as the `admin_token` that was set in the `msc3861` section - - Optionally, remove the client provisionned for Synapse in the `clients` section of the MAS configuration + - Optionally, remove the client provisioned for Synapse in the `clients` section of the MAS configuration From a4cafa8ed056be1632988d03d9af55e30d583a59 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 18 Aug 2025 13:46:25 +0200 Subject: [PATCH 4/5] Reformat with rustfmt --- crates/cli/src/commands/doctor.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/commands/doctor.rs b/crates/cli/src/commands/doctor.rs index 0e24d4d92..1542b70b4 100644 --- a/crates/cli/src/commands/doctor.rs +++ b/crates/cli/src/commands/doctor.rs @@ -291,9 +291,10 @@ Error details: {e} Ok(response) => { let status = response.status(); // We intentionally omit the required 'localpart' parameter in this request. - // If authentication is successful, Synapse returns a 400 Bad Request because of the missing parameter. - // If authentication fails, Synapse will return a 403 Forbidden. - // If the MAS integration isn't enabled, Synapse will return a 404 Not found. + // If authentication is successful, Synapse returns a 400 Bad Request because of + // the missing parameter. If authentication fails, Synapse + // will return a 403 Forbidden. If the MAS integration isn't + // enabled, Synapse will return a 404 Not found. if status == StatusCode::BAD_REQUEST { info!( r#"✅ The Synapse admin API is reachable with authentication at "{mas_api}"."# From 6d4747cd2866935bddd1334c2221da27c3993c94 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Sep 2025 14:08:20 +0200 Subject: [PATCH 5/5] Fix the wording about admin token vs. secret in the doctor command --- crates/cli/src/commands/doctor.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/cli/src/commands/doctor.rs b/crates/cli/src/commands/doctor.rs index 1542b70b4..636741487 100644 --- a/crates/cli/src/commands/doctor.rs +++ b/crates/cli/src/commands/doctor.rs @@ -44,7 +44,7 @@ impl Options { r"The homeserver host in the config (`matrix.homeserver`) is not a valid domain. See {DOCS_BASE}/setup/homeserver.html", )?; - let admin_token = config.matrix.secret().await?; + let secret = config.matrix.secret().await?; let hs_api = config.matrix.endpoint; if !issuer.starts_with("https://") { @@ -280,49 +280,50 @@ Error details: {e} ), } - // Try to reach an authenticated admin API endpoint + // Try to reach an authenticated MAS API endpoint let mas_api = hs_api.join("/_synapse/mas/is_localpart_available")?; let result = http_client .get(mas_api.as_str()) - .bearer_auth(&admin_token) + .bearer_auth(&secret) .send_traced() .await; match result { Ok(response) => { let status = response.status(); - // We intentionally omit the required 'localpart' parameter in this request. - // If authentication is successful, Synapse returns a 400 Bad Request because of - // the missing parameter. If authentication fails, Synapse - // will return a 403 Forbidden. If the MAS integration isn't - // enabled, Synapse will return a 404 Not found. + // We intentionally omit the required 'localpart' parameter + // in this request. If authentication is successful, Synapse + // returns a 400 Bad Request because of the missing + // parameter. If authentication fails, Synapse will return a + // 403 Forbidden. If the MAS integration isn't enabled, + // Synapse will return a 404 Not found. if status == StatusCode::BAD_REQUEST { info!( - r#"✅ The Synapse admin API is reachable with authentication at "{mas_api}"."# + r#"✅ The Synapse MAS API is reachable with authentication at "{mas_api}"."# ); } else { error!( - r#"❌ A Synapse admin API endpoint at "{mas_api}" replied with {status}. + r#"❌ A Synapse MAS API endpoint at "{mas_api}" replied with {status}. Make sure the homeserver is running, and that the MAS config has the correct `matrix.secret`. -It should match the `admin_token` set in the Synapse config. +It should match the `secret` set in the Synapse config. matrix_authentication_service: enabled: true endpoint: {issuer:?} # This must exactly match the secret in the MAS config: - secret: {admin_token:?} + secret: {secret:?} And in the MAS config: matrix: homeserver: "{matrix_domain}" endpoint: "{hs_api}" - secret: {admin_token:?} + secret: {secret:?} "# ); } } Err(e) => error!( - r#"❌ Can't reach the Synapse admin API at "{mas_api}". + r#"❌ Can't reach the Synapse MAS API at "{mas_api}". Make sure the homeserver is running, and that the MAS config has the correct `matrix.secret`. Error details: {e}