Merge remote-tracking branch 'origin/main' into quenting/stable-api

This commit is contained in:
Quentin Gliech
2025-08-04 16:38:49 +02:00
818 changed files with 9884 additions and 5291 deletions

View File

@@ -1,3 +1,8 @@
# 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.
[build]
rustflags = ["--cfg", "tokio_unstable"]

View File

@@ -1,3 +1,8 @@
# 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.
comment: false
flag_management:

View File

@@ -1,2 +1,7 @@
# 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.
[profile.default]
retries = 1

View File

@@ -1,3 +1,8 @@
# 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.
target/
crates/*/target
crates/*/node_modules

View File

@@ -1,3 +1,8 @@
# 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.
root = true
[*]

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.wasm binary

View File

@@ -1,3 +1,8 @@
# 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.
name: Build the frontend assets
description: Installs Node.js and builds the frontend assets from the frontend directory
@@ -7,7 +12,7 @@ runs:
- name: Install Node
uses: actions/setup-node@v4.2.0
with:
node-version: '22'
node-version: "22"
- name: Install dependencies
run: npm ci

View File

@@ -1,3 +1,8 @@
# 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.
name: Build the Open Policy Agent policies
description: Installs OPA and builds the policies

View File

@@ -1,3 +1,8 @@
# 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.
version: 2
updates:
- package-ecosystem: "cargo"

5
.github/release.yml vendored
View File

@@ -1,3 +1,8 @@
# 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.
changelog:
categories:
- title: Bug Fixes

View File

@@ -1,2 +1,7 @@
# 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.
node_modules/
package-lock.json

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// @ts-check

View File

@@ -1,3 +1,8 @@
# 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.
name: Build
on:
@@ -221,7 +226,7 @@ jobs:
steps:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5.7.0
uses: docker/metadata-action@v5.8.0
with:
images: "${{ env.IMAGE }}"
bake-target: docker-metadata-action
@@ -237,7 +242,7 @@ jobs:
- name: Docker meta (debug variant)
id: meta-debug
uses: docker/metadata-action@v5.7.0
uses: docker/metadata-action@v5.8.0
with:
images: "${{ env.IMAGE }}"
bake-target: docker-metadata-action-debug
@@ -253,10 +258,10 @@ jobs:
type=sha
- name: Setup Cosign
uses: sigstore/cosign-installer@v3.8.2
uses: sigstore/cosign-installer@v3.9.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0
uses: docker/setup-buildx-action@v3.11.1
with:
buildkitd-config-inline: |
[registry."docker.io"]

View File

@@ -1,3 +1,8 @@
# 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.
name: CI
on:
@@ -153,7 +158,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Run `cargo-deny`
uses: EmbarkStudios/cargo-deny-action@v2.0.11
uses: EmbarkStudios/cargo-deny-action@v2.0.12
with:
rust-version: stable
@@ -210,7 +215,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.86.0
uses: dtolnay/rust-toolchain@1.87.0
with:
components: clippy

View File

@@ -1,3 +1,8 @@
# 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.
name: Coverage
on:

View File

@@ -1,3 +1,8 @@
# 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.
name: Build and deploy the documentation
on:

View File

@@ -1,3 +1,8 @@
# 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.
name: Merge back a reference to main
on:
workflow_call:

View File

@@ -1,3 +1,8 @@
# 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.
name: Create a new release branch
on:
workflow_dispatch:

View File

@@ -1,3 +1,8 @@
# 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.
name: Bump the version on a release branch
on:
workflow_dispatch:

View File

@@ -1,3 +1,8 @@
# 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.
name: Tag a new version
on:
workflow_call:

View File

@@ -1,3 +1,8 @@
# 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.
name: Download translation files from Localazy
on:
workflow_dispatch:

View File

@@ -1,3 +1,8 @@
# 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.
name: Upload translation files to Localazy
on:
push:

7
.gitignore vendored
View File

@@ -1,5 +1,10 @@
# 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.
# Rust
target/
target
# Editors
.idea

View File

@@ -1,3 +1,8 @@
# 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.
max_width = 100
comment_width = 80
wrap_comments = true

1484
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,16 @@
# 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.
[workspace]
default-members = ["crates/cli"]
members = ["crates/*"]
resolver = "2"
# Updated in the CI with a `sed` command
package.version = "0.17.0-rc.0"
package.license = "AGPL-3.0-only"
package.version = "0.20.0"
package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial"
package.authors = ["Element Backend Team"]
package.edition = "2024"
package.homepage = "https://element-hq.github.io/matrix-authentication-service/"
@@ -21,6 +26,7 @@ all = { level = "deny", priority = -1 }
pedantic = { level = "warn", priority = -1 }
str_to_string = "deny"
too_many_lines = "allow"
[workspace.lints.rustdoc]
broken_intra_doc_links = "deny"
@@ -28,35 +34,35 @@ broken_intra_doc_links = "deny"
[workspace.dependencies]
# Workspace crates
mas-axum-utils = { path = "./crates/axum-utils/", version = "=0.17.0-rc.0" }
mas-cli = { path = "./crates/cli/", version = "=0.17.0-rc.0" }
mas-config = { path = "./crates/config/", version = "=0.17.0-rc.0" }
mas-context = { path = "./crates/context/", version = "=0.17.0-rc.0" }
mas-data-model = { path = "./crates/data-model/", version = "=0.17.0-rc.0" }
mas-email = { path = "./crates/email/", version = "=0.17.0-rc.0" }
mas-graphql = { path = "./crates/graphql/", version = "=0.17.0-rc.0" }
mas-handlers = { path = "./crates/handlers/", version = "=0.17.0-rc.0" }
mas-http = { path = "./crates/http/", version = "=0.17.0-rc.0" }
mas-i18n = { path = "./crates/i18n/", version = "=0.17.0-rc.0" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=0.17.0-rc.0" }
mas-iana = { path = "./crates/iana/", version = "=0.17.0-rc.0" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=0.17.0-rc.0" }
mas-jose = { path = "./crates/jose/", version = "=0.17.0-rc.0" }
mas-keystore = { path = "./crates/keystore/", version = "=0.17.0-rc.0" }
mas-listener = { path = "./crates/listener/", version = "=0.17.0-rc.0" }
mas-matrix = { path = "./crates/matrix/", version = "=0.17.0-rc.0" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=0.17.0-rc.0" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=0.17.0-rc.0" }
mas-policy = { path = "./crates/policy/", version = "=0.17.0-rc.0" }
mas-router = { path = "./crates/router/", version = "=0.17.0-rc.0" }
mas-spa = { path = "./crates/spa/", version = "=0.17.0-rc.0" }
mas-storage = { path = "./crates/storage/", version = "=0.17.0-rc.0" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=0.17.0-rc.0" }
mas-tasks = { path = "./crates/tasks/", version = "=0.17.0-rc.0" }
mas-templates = { path = "./crates/templates/", version = "=0.17.0-rc.0" }
mas-tower = { path = "./crates/tower/", version = "=0.17.0-rc.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=0.17.0-rc.0" }
syn2mas = { path = "./crates/syn2mas", version = "=0.17.0-rc.0" }
mas-axum-utils = { path = "./crates/axum-utils/", version = "=0.20.0" }
mas-cli = { path = "./crates/cli/", version = "=0.20.0" }
mas-config = { path = "./crates/config/", version = "=0.20.0" }
mas-context = { path = "./crates/context/", version = "=0.20.0" }
mas-data-model = { path = "./crates/data-model/", version = "=0.20.0" }
mas-email = { path = "./crates/email/", version = "=0.20.0" }
mas-graphql = { path = "./crates/graphql/", version = "=0.20.0" }
mas-handlers = { path = "./crates/handlers/", version = "=0.20.0" }
mas-http = { path = "./crates/http/", version = "=0.20.0" }
mas-i18n = { path = "./crates/i18n/", version = "=0.20.0" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=0.20.0" }
mas-iana = { path = "./crates/iana/", version = "=0.20.0" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=0.20.0" }
mas-jose = { path = "./crates/jose/", version = "=0.20.0" }
mas-keystore = { path = "./crates/keystore/", version = "=0.20.0" }
mas-listener = { path = "./crates/listener/", version = "=0.20.0" }
mas-matrix = { path = "./crates/matrix/", version = "=0.20.0" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=0.20.0" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=0.20.0" }
mas-policy = { path = "./crates/policy/", version = "=0.20.0" }
mas-router = { path = "./crates/router/", version = "=0.20.0" }
mas-spa = { path = "./crates/spa/", version = "=0.20.0" }
mas-storage = { path = "./crates/storage/", version = "=0.20.0" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=0.20.0" }
mas-tasks = { path = "./crates/tasks/", version = "=0.20.0" }
mas-templates = { path = "./crates/templates/", version = "=0.20.0" }
mas-tower = { path = "./crates/tower/", version = "=0.20.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=0.20.0" }
syn2mas = { path = "./crates/syn2mas", version = "=0.20.0" }
# OpenAPI schema generation and validation
[workspace.dependencies.aide]
@@ -149,7 +155,7 @@ version = "0.15.11"
# Cookie store
[workspace.dependencies.cookie_store]
version = "0.21.1"
version = "0.22.0"
default-features = false
features = ["serde_json"]
@@ -161,7 +167,7 @@ features = ["serde", "clock"]
# CLI argument parsing
[workspace.dependencies.clap]
version = "4.5.40"
version = "4.5.42"
features = ["derive"]
# Object Identifiers (OIDs) as constants
@@ -268,7 +274,7 @@ features = ["client", "server", "http1", "http2"]
# Additional Hyper utilties
[workspace.dependencies.hyper-util]
version = "0.1.14"
version = "0.1.16"
features = [
"client",
"server",
@@ -315,7 +321,7 @@ features = ["std"]
# HashMap which preserves insertion order
[workspace.dependencies.indexmap]
version = "2.9.0"
version = "2.10.0"
features = ["serde"]
# Indented string literals
@@ -348,10 +354,12 @@ features = ["serde"]
# Email sending
[workspace.dependencies.lettre]
version = "0.11.15"
version = "0.11.18"
default-features = false
features = [
"tokio1-rustls-tls",
"tokio1-rustls",
"rustls-platform-verifier",
"aws-lc-rs",
"hostname",
"builder",
"tracing",
@@ -370,12 +378,12 @@ version = "0.3.17"
# Templates
[workspace.dependencies.minijinja]
version = "2.10.2"
version = "2.11.0"
features = ["loader", "json", "speedups", "unstable_machinery"]
# Additional filters for minijinja
[workspace.dependencies.minijinja-contrib]
version = "2.10.2"
version = "2.11.0"
features = ["pycompat"]
# Utilities to deal with non-zero values
@@ -384,40 +392,42 @@ version = "0.3.0"
# Open Policy Agent support through WASM
[workspace.dependencies.opa-wasm]
version = "0.1.5"
version = "0.1.7"
# OpenTelemetry
[workspace.dependencies.opentelemetry]
version = "0.29.1"
version = "0.30.0"
features = ["trace", "metrics"]
[workspace.dependencies.opentelemetry-http]
version = "0.29.0"
version = "0.30.0"
features = ["reqwest"]
[workspace.dependencies.opentelemetry-jaeger-propagator]
version = "0.29.0"
version = "0.30.0"
[workspace.dependencies.opentelemetry-otlp]
version = "0.29.0"
version = "0.30.0"
default-features = false
features = ["trace", "metrics", "http-proto"]
[workspace.dependencies.opentelemetry-prometheus]
version = "0.29.1"
# https://github.com/open-telemetry/opentelemetry-rust/pull/3076
git = "https://github.com/sandhose/opentelemetry-rust.git"
branch = "otel-prometheus-0.30"
[workspace.dependencies.opentelemetry-resource-detectors]
version = "0.8.0"
version = "0.9.0"
[workspace.dependencies.opentelemetry-semantic-conventions]
version = "0.29.0"
version = "0.30.0"
features = ["semconv_experimental"]
[workspace.dependencies.opentelemetry-stdout]
version = "0.29.0"
version = "0.30.0"
features = ["trace", "metrics"]
[workspace.dependencies.opentelemetry_sdk]
version = "0.29.0"
version = "0.30.0"
features = [
"experimental_trace_batch_span_processor_with_async_runtime",
"experimental_metrics_periodicreader_with_async_runtime",
"rt-tokio",
]
[workspace.dependencies.tracing-opentelemetry]
version = "0.30.0"
version = "0.31.0"
default-features = false
# P256 elliptic curve
@@ -446,11 +456,11 @@ features = ["std"]
# Parser generator
[workspace.dependencies.pest]
version = "2.8.0"
version = "2.8.1"
# Pest derive macros
[workspace.dependencies.pest_derive]
version = "2.8.0"
version = "2.8.1"
# Pin projection
[workspace.dependencies.pin-project-lite]
@@ -468,7 +478,7 @@ features = ["std", "pkcs5", "encryption"]
# Public Suffix List
[workspace.dependencies.psl]
version = "2.1.120"
version = "2.1.127"
# Prometheus metrics
[workspace.dependencies.prometheus]
@@ -492,9 +502,15 @@ version = "1.11.1"
# High-level HTTP client
[workspace.dependencies.reqwest]
version = "0.12.20"
version = "0.12.22"
default-features = false
features = ["http2", "rustls-tls-manual-roots", "charset", "json", "socks"]
features = [
"http2",
"rustls-tls-manual-roots-no-provider",
"charset",
"json",
"socks",
]
# RSA cryptography
[workspace.dependencies.rsa]
@@ -507,11 +523,11 @@ version = "2.1.1"
# Matrix-related types
[workspace.dependencies.ruma-common]
version = "0.15.2"
version = "0.15.4"
# TLS stack
[workspace.dependencies.rustls]
version = "0.23.27"
version = "0.23.31"
# PEM parsing for rustls
[workspace.dependencies.rustls-pemfile]
@@ -523,7 +539,7 @@ version = "1.12.0"
# Use platform-specific verifier for TLS
[workspace.dependencies.rustls-platform-verifier]
version = "0.5.3"
version = "0.6.0"
# systemd service status notification
[workspace.dependencies.sd-notify]
@@ -557,18 +573,18 @@ features = [
# Sentry error tracking
[workspace.dependencies.sentry]
version = "0.37.0"
version = "0.42.0"
default-features = false
features = ["backtrace", "contexts", "panic", "tower", "reqwest"]
# Sentry tower layer
[workspace.dependencies.sentry-tower]
version = "0.37.0"
version = "0.42.0"
features = ["http", "axum-matched-path"]
# Sentry tracing integration
[workspace.dependencies.sentry-tracing]
version = "0.37.0"
version = "0.42.0"
# Serialization and deserialization
[workspace.dependencies.serde]
@@ -577,7 +593,7 @@ features = ["derive"] # Most of the time, if we need serde, we need derive
# JSON serialization and deserialization
[workspace.dependencies.serde_json]
version = "1.0.140"
version = "1.0.142"
features = ["preserve_order"]
# URL encoded form serialization
@@ -586,7 +602,7 @@ version = "0.7.1"
# Custom serialization helpers
[workspace.dependencies.serde_with]
version = "3.12.0"
version = "3.14.0"
features = ["hex", "chrono"]
# YAML serialization
@@ -604,7 +620,7 @@ version = "2.2.0"
# Low-level socket manipulation
[workspace.dependencies.socket2]
version = "0.5.10"
version = "0.6.0"
# Subject Public Key Info
[workspace.dependencies.spki]
@@ -630,11 +646,11 @@ features = [
version = "2.0.12"
[workspace.dependencies.thiserror-ext]
version = "0.2.1"
version = "0.3.0"
# Async runtime
[workspace.dependencies.tokio]
version = "1.45.1"
version = "1.47.1"
features = ["full"]
[workspace.dependencies.tokio-stream]
@@ -713,7 +729,7 @@ version = "2.5.0"
# HTTP mock server
[workspace.dependencies.wiremock]
version = "0.6.3"
version = "0.6.4"
# User-agent parser
[workspace.dependencies.woothee]

View File

@@ -1,4 +1,8 @@
# syntax = docker/dockerfile:1.7.1
# 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.
# Builds a minimal image with the binary only. It is multi-arch capable,
# cross-building to aarch64 and x86_64. When cross-compiling, Docker sets two
@@ -8,7 +12,7 @@
# The Debian version and version name must be in sync
ARG DEBIAN_VERSION=12
ARG DEBIAN_VERSION_NAME=bookworm
ARG RUSTC_VERSION=1.86.0
ARG RUSTC_VERSION=1.87.0
ARG NODEJS_VERSION=20.15.0
ARG OPA_VERSION=1.1.0
ARG CARGO_AUDITABLE_VERSION=0.6.6

6
LICENSE-COMMERCIAL Normal file
View File

@@ -0,0 +1,6 @@
Licensees holding a valid commercial license with Element may use this
software in accordance with the terms contained in a written agreement
between you and Element.
To purchase a commercial license please contact our sales team at
licensing@element.io

View File

@@ -27,3 +27,15 @@ Anyone can contribute to translations through [Localazy](https://localazy.com/el
## 🏗️ Contributing
See the [contribution guidelines](https://element-hq.github.io/matrix-authentication-service/development/contributing.html) for information on how to contribute to this project.
## ⚖️ Copyright & License
Copyright 2024, 2025 New Vector Ltd.
Copyright 2021-2024 The Matrix.org Foundation C.I.C.
This software is dual-licensed by New Vector Ltd (Element). It can be used either:
(1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR
(2) under the terms of a paid-for Element Commercial License agreement between you and Element (the terms of which may vary depending on what you and Element have agreed to).
Unless required by applicable law or agreed to in writing, software distributed under the Licenses is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licenses for the specific language governing permissions and limitations under the Licenses.

View File

@@ -1,28 +1,27 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignore": [
".devcontainer/**",
"docs/**",
"translations/**",
"policies/**",
"crates/**",
"frontend/package.json",
"frontend/src/gql/**",
"frontend/src/routeTree.gen.ts",
"frontend/.storybook/locales.ts",
"frontend/.storybook/public/mockServiceWorker.js",
"frontend/locales/*.json",
"**/coverage/**",
"**/dist/**"
"includes": [
"**",
"!**/.devcontainer/**",
"!**/docs/**",
"!**/translations/**",
"!**/policies/**",
"!**/crates/**",
"!**/frontend/package.json",
"!**/frontend/src/gql/**",
"!**/frontend/src/routeTree.gen.ts",
"!**/frontend/.storybook/locales.ts",
"!**/frontend/.storybook/public/mockServiceWorker.js",
"!**/frontend/locales/**/*.json",
"!**/coverage/**",
"!**/dist/**"
]
},
"formatter": {
@@ -36,6 +35,19 @@
"correctness": {
"noUnusedImports": "warn",
"noUnusedVariables": "warn"
},
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error",
"noDescendingSpecificity": "off"
}
}
}

View File

@@ -1,3 +1,8 @@
# 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.
# Documentation for possible options in this file is at
# https://rust-lang.github.io/mdBook/format/config.html
[book]

View File

@@ -1,3 +1,8 @@
# 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.
doc-valid-idents = ["OpenID", "OAuth", "..", "PostgreSQL", "SQLite"]
disallowed-methods = [
@@ -10,7 +15,6 @@ disallowed-methods = [
]
disallowed-types = [
"rand::OsRng",
{ path = "std::path::PathBuf", reason = "use camino::Utf8PathBuf instead" },
{ path = "std::path::Path", reason = "use camino::Utf8Path instead" },
]

View File

@@ -1,3 +1,8 @@
# 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.
[package]
name = "mas-axum-utils"
version.workspace = true

View File

@@ -1,21 +1,20 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::collections::HashMap;
use axum::{
BoxError, Json,
extract::{
Form, FromRequest, FromRequestParts,
Form, FromRequest,
rejection::{FailedToDeserializeForm, FormRejection},
},
response::IntoResponse,
};
use axum_extra::typed_header::{TypedHeader, TypedHeaderRejectionReason};
use headers::{Authorization, authorization::Basic};
use headers::authorization::{Basic, Bearer, Credentials as _};
use http::{Request, StatusCode};
use mas_data_model::{Client, JwksOrJwksUri};
use mas_http::RequestBuilderExt;
@@ -60,17 +59,30 @@ pub enum Credentials {
client_id: String,
jwt: Box<Jwt<'static, HashMap<String, serde_json::Value>>>,
},
BearerToken {
token: String,
},
}
impl Credentials {
/// Get the `client_id` of the credentials
#[must_use]
pub fn client_id(&self) -> &str {
pub fn client_id(&self) -> Option<&str> {
match self {
Credentials::None { client_id }
| Credentials::ClientSecretBasic { client_id, .. }
| Credentials::ClientSecretPost { client_id, .. }
| Credentials::ClientAssertionJwtBearer { client_id, .. } => client_id,
| Credentials::ClientAssertionJwtBearer { client_id, .. } => Some(client_id),
Credentials::BearerToken { .. } => None,
}
}
/// Get the bearer token from the credentials.
#[must_use]
pub fn bearer_token(&self) -> Option<&str> {
match self {
Credentials::BearerToken { token } => Some(token),
_ => None,
}
}
@@ -89,6 +101,7 @@ impl Credentials {
| Credentials::ClientSecretBasic { client_id, .. }
| Credentials::ClientSecretPost { client_id, .. }
| Credentials::ClientAssertionJwtBearer { client_id, .. } => client_id,
Credentials::BearerToken { .. } => return Ok(None),
};
repo.oauth2_client().find_by_client_id(client_id).await
@@ -239,7 +252,7 @@ pub struct ClientAuthorization<F = ()> {
impl<F> ClientAuthorization<F> {
/// Get the `client_id` from the credentials.
#[must_use]
pub fn client_id(&self) -> &str {
pub fn client_id(&self) -> Option<&str> {
self.credentials.client_id()
}
}
@@ -355,30 +368,40 @@ where
{
type Rejection = ClientAuthorizationError;
#[allow(clippy::too_many_lines)]
async fn from_request(
req: Request<axum::body::Body>,
state: &S,
) -> Result<Self, Self::Rejection> {
// Split the request into parts so we can extract some headers
let (mut parts, body) = req.into_parts();
enum Authorization {
Basic(String, String),
Bearer(String),
}
let header =
TypedHeader::<Authorization<Basic>>::from_request_parts(&mut parts, state).await;
// Take the Authorization header
let credentials_from_header = match header {
Ok(header) => Some((header.username().to_owned(), header.password().to_owned())),
Err(err) => match err.reason() {
// If it's missing it is fine
TypedHeaderRejectionReason::Missing => None,
// If the header could not be parsed, return the error
_ => return Err(ClientAuthorizationError::InvalidHeader),
},
// Sadly, the typed-header 'Authorization' doesn't let us check for both
// Basic and Bearer at the same time, so we need to parse them manually
let authorization = if let Some(header) = req.headers().get(http::header::AUTHORIZATION) {
let bytes = header.as_bytes();
if bytes.len() >= 6 && bytes[..6].eq_ignore_ascii_case(b"Basic ") {
let Some(decoded) = Basic::decode(header) else {
return Err(ClientAuthorizationError::InvalidHeader);
};
// Reconstruct the request from the parts
let req = Request::from_parts(parts, body);
Some(Authorization::Basic(
decoded.username().to_owned(),
decoded.password().to_owned(),
))
} else if bytes.len() >= 7 && bytes[..7].eq_ignore_ascii_case(b"Bearer ") {
let Some(decoded) = Bearer::decode(header) else {
return Err(ClientAuthorizationError::InvalidHeader);
};
Some(Authorization::Bearer(decoded.token().to_owned()))
} else {
return Err(ClientAuthorizationError::InvalidHeader);
}
} else {
None
};
// Take the form value
let (
@@ -407,13 +430,19 @@ where
// And now, figure out the actual auth method
let credentials = match (
credentials_from_header,
authorization,
client_id_from_form,
client_secret_from_form,
client_assertion_type,
client_assertion,
) {
(Some((client_id, client_secret)), client_id_from_form, None, None, None) => {
(
Some(Authorization::Basic(client_id, client_secret)),
client_id_from_form,
None,
None,
None,
) => {
if let Some(client_id_from_form) = client_id_from_form {
// If the client_id was in the body, verify it matches with the header
if client_id != client_id_from_form {
@@ -483,6 +512,11 @@ where
});
}
(Some(Authorization::Bearer(token)), None, None, None, None) => {
// Got a bearer token
Credentials::BearerToken { token }
}
(None, None, None, None, None) => {
// Special case when there are no credentials anywhere
return Err(ClientAuthorizationError::MissingCredentials);
@@ -677,4 +711,29 @@ mod tests {
jwt.verify_with_shared_secret(b"client-secret".to_vec())
.unwrap();
}
#[tokio::test]
async fn bearer_token_test() {
let req = Request::builder()
.method(Method::POST)
.header(
http::header::CONTENT_TYPE,
mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(),
)
.header(http::header::AUTHORIZATION, "Bearer token")
.body(Body::new("foo=bar".to_owned()))
.unwrap();
assert_eq!(
ClientAuthorization::<serde_json::Value>::from_request(req, &())
.await
.unwrap(),
ClientAuthorization {
credentials: Credentials::BearerToken {
token: "token".to_owned(),
},
form: Some(serde_json::json!({"foo": "bar"})),
}
);
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//! Private (encrypted) cookie jar, based on axum-extra's cookie jar

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use base64ct::{Base64UrlUnpadded, Encoding};
use chrono::{DateTime, Duration, Utc};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use axum::response::{IntoResponse, Response};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use axum::{
Extension,

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use axum::response::{IntoResponse, Response};
use axum_extra::typed_header::TypedHeader;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::cmp::Reverse;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
#![deny(clippy::future_not_send)]
#![allow(clippy::module_name_repetitions)]

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::convert::Infallible;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use mas_data_model::BrowserSession;
use mas_storage::RepositoryAccess;

View File

@@ -1,8 +1,8 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{collections::HashMap, error::Error};

View File

@@ -1,3 +1,8 @@
# 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.
[package]
name = "mas-cli"
version.workspace = true

View File

@@ -1,7 +1,7 @@
// Copyright 2024, 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use vergen_gitcl::{Emitter, GitclBuilder, RustcBuilder};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{convert::Infallible, net::IpAddr, sync::Arc};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::process::ExitCode;
@@ -72,7 +72,7 @@ impl Options {
SC::Dump { output } => {
let _span = info_span!("cli.config.dump").entered();
let config = RootConfig::extract(figment)?;
let config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let config = serde_yaml::to_string(&config)?;
if let Some(output) = output {
@@ -88,7 +88,7 @@ impl Options {
SC::Check => {
let _span = info_span!("cli.config.check").entered();
let _config = RootConfig::extract(figment)?;
let _config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
info!("Configuration file looks good");
}
@@ -105,7 +105,8 @@ impl Options {
if !synapse_config.is_empty() {
info!("Adjusting MAS config to match Synapse config from {synapse_config:?}");
let synapse_config = syn2mas::synapse_config::Config::load(&synapse_config)?;
let synapse_config = syn2mas::synapse_config::Config::load(&synapse_config)
.map_err(anyhow::Error::from_boxed)?;
config = synapse_config.adjust_mas_config(config, &mut rng, clock.now());
}
@@ -121,7 +122,7 @@ impl Options {
}
SC::Sync { prune, dry_run } => {
let config = SyncConfig::extract(figment)?;
let config = SyncConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let clock = SystemClock::default();
let encrypter = config.secrets.encrypter().await?;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::process::ExitCode;
@@ -30,7 +30,8 @@ enum Subcommand {
impl Options {
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
let _span = info_span!("cli.database.migrate").entered();
let config = DatabaseConfig::extract_or_default(figment)?;
let config =
DatabaseConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&config).await?;
// Run pending migrations

View File

@@ -1,8 +1,8 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::process::ExitCode;
@@ -41,13 +41,16 @@ impl Options {
match self.subcommand {
SC::Policy { with_dynamic_data } => {
let _span = info_span!("cli.debug.policy").entered();
let config = PolicyConfig::extract_or_default(figment)?;
let matrix_config = MatrixConfig::extract(figment)?;
let config =
PolicyConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?;
let matrix_config =
MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
info!("Loading and compiling the policy module");
let policy_factory = policy_factory_from_config(&config, &matrix_config).await?;
if with_dynamic_data {
let database_config = DatabaseConfig::extract(figment)?;
let database_config =
DatabaseConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let pool = database_pool_from_config(&database_config).await?;
let repository_factory = PgRepositoryFactory::new(pool.clone());
load_policy_factory_dynamic_data(&policy_factory, &repository_factory).await?;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//! Diagnostic utility to check the health of the deployment
//!
@@ -26,14 +26,13 @@ const DOCS_BASE: &str = "https://element-hq.github.io/matrix-authentication-serv
pub(super) struct Options {}
impl Options {
#[allow(clippy::too_many_lines)]
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
let _span = info_span!("cli.doctor").entered();
info!(
"💡 Running diagnostics, make sure that both MAS and Synapse are running, and that MAS is using the same configuration files as this tool."
);
let config = RootConfig::extract(figment)?;
let config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
// We'll need an HTTP client
let http_client = mas_http::reqwest_client();

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{collections::BTreeMap, process::ExitCode};
@@ -149,6 +149,10 @@ enum Subcommand {
UnlockUser {
/// User to unlock
username: String,
/// Whether to reactivate the user if it had been deactivated
#[arg(long)]
reactivate: bool,
},
/// Register a user
@@ -203,7 +207,6 @@ enum Subcommand {
}
impl Options {
#[allow(clippy::too_many_lines)]
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
use Subcommand as SC;
let clock = SystemClock::default();
@@ -219,8 +222,10 @@ impl Options {
let _span =
info_span!("cli.manage.set_password", user.username = %username).entered();
let database_config = DatabaseConfig::extract_or_default(figment)?;
let passwords_config = PasswordsConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let passwords_config = PasswordsConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&database_config).await?;
let password_manager = password_manager_from_config(&passwords_config).await?;
@@ -260,7 +265,8 @@ impl Options {
)
.entered();
let database_config = DatabaseConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&database_config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -314,7 +320,12 @@ impl Options {
admin,
device_id,
} => {
let database_config = DatabaseConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let matrix_config =
MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let http_client = mas_http::reqwest_client();
let homeserver = homeserver_connection_from_config(&matrix_config, http_client);
let mut conn = database_connection_from_config(&database_config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -331,6 +342,24 @@ impl Options {
Device::generate(&mut rng)
};
if let Err(e) = homeserver
.upsert_device(&user.username, device.as_str(), None)
.await
{
error!(
error = &*e,
"Could not create the device on the homeserver, aborting"
);
// Schedule a device sync job to remove the potential leftover device
repo.queue_job()
.schedule_job(&mut rng, &clock, SyncDevicesJob::new(&user))
.await?;
repo.into_inner().commit().await?;
return Ok(ExitCode::FAILURE);
}
let compat_session = repo
.compat_session()
.add(&mut rng, &clock, &user, device, None, admin, None)
@@ -372,7 +401,8 @@ impl Options {
(Some(_), true) => unreachable!(), // This should be handled by the clap group
};
let database_config = DatabaseConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&database_config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -399,7 +429,8 @@ impl Options {
SC::ProvisionAllUsers => {
let _span = info_span!("cli.manage.provision_all_users").entered();
let database_config = DatabaseConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&database_config).await?;
let mut txn = conn.begin().await?;
@@ -425,7 +456,8 @@ impl Options {
SC::KillSessions { username, dry_run } => {
let _span =
info_span!("cli.manage.kill_sessions", user.username = username).entered();
let database_config = DatabaseConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&database_config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -497,7 +529,8 @@ impl Options {
deactivate,
} => {
let _span = info_span!("cli.manage.lock_user", user.username = username).entered();
let config = DatabaseConfig::extract_or_default(figment)?;
let config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -527,9 +560,14 @@ impl Options {
Ok(ExitCode::SUCCESS)
}
SC::UnlockUser { username } => {
let _span = info_span!("cli.manage.lock_user", user.username = username).entered();
let config = DatabaseConfig::extract_or_default(figment)?;
SC::UnlockUser {
username,
reactivate,
} => {
let _span =
info_span!("cli.manage.unlock_user", user.username = username).entered();
let config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let mut conn = database_connection_from_config(&config).await?;
let txn = conn.begin().await?;
let mut repo = PgRepository::from_conn(txn);
@@ -540,10 +578,14 @@ impl Options {
.await?
.context("User not found")?;
warn!(%user.id, "User scheduling user reactivation");
if reactivate {
warn!(%user.id, "Scheduling user reactivation");
repo.queue_job()
.schedule_job(&mut rng, &clock, ReactivateUserJob::new(&user))
.await?;
} else {
repo.user().unlock(user).await?;
}
repo.into_inner().commit().await?;
@@ -562,9 +604,12 @@ impl Options {
ignore_password_complexity,
} => {
let http_client = mas_http::reqwest_client();
let password_config = PasswordsConfig::extract_or_default(figment)?;
let database_config = DatabaseConfig::extract_or_default(figment)?;
let matrix_config = MatrixConfig::extract(figment)?;
let password_config = PasswordsConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let database_config = DatabaseConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let matrix_config =
MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let password_manager = password_manager_from_config(&password_config).await?;
let homeserver = homeserver_connection_from_config(&matrix_config, http_client);

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::process::ExitCode;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{collections::BTreeSet, process::ExitCode, sync::Arc, time::Duration};
@@ -55,11 +55,10 @@ pub(super) struct Options {
}
impl Options {
#[allow(clippy::too_many_lines)]
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
let span = info_span!("cli.run.init").entered();
let mut shutdown = LifecycleManager::new()?;
let config = AppConfig::extract(figment)?;
let config = AppConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
info!(version = crate::VERSION, "Starting up");
@@ -101,8 +100,10 @@ impl Options {
} else {
// Sync the configuration with the database
let mut conn = pool.acquire().await?;
let clients_config = ClientsConfig::extract_or_default(figment)?;
let upstream_oauth2_config = UpstreamOAuth2Config::extract_or_default(figment)?;
let clients_config =
ClientsConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?;
let upstream_oauth2_config = UpstreamOAuth2Config::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
crate::sync::config_sync(
upstream_oauth2_config,
@@ -173,8 +174,9 @@ impl Options {
test_mailer_in_background(&mailer, Duration::from_secs(30));
info!("Starting task worker");
mas_tasks::init(
mas_tasks::init_and_run(
PgRepositoryFactory::new(pool.clone()),
SystemClock::default(),
&mailer,
homeserver_connection.clone(),
url_builder.clone(),

View File

@@ -1,7 +1,7 @@
// Copyright 2024, 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{collections::HashMap, process::ExitCode, time::Duration};
@@ -88,7 +88,6 @@ const NUM_WRITER_CONNECTIONS: usize = 8;
impl Options {
#[tracing::instrument("cli.syn2mas.run", skip_all)]
#[allow(clippy::too_many_lines)]
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
if self.synapse_configuration_files.is_empty() {
error!("Please specify the path to the Synapse configuration file(s).");
@@ -96,6 +95,7 @@ impl Options {
}
let synapse_config = synapse_config::Config::load(&self.synapse_configuration_files)
.map_err(anyhow::Error::from_boxed)
.context("Failed to load Synapse configuration")?;
// Establish a connection to Synapse's Postgres database
@@ -111,7 +111,8 @@ impl Options {
.await
.context("could not connect to Synapse Postgres database")?;
let config = DatabaseConfig::extract_or_default(figment)?;
let config =
DatabaseConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?;
let mut mas_connection = database_connection_from_config_with_options(
&config,
@@ -131,7 +132,7 @@ impl Options {
// First perform a config sync
// This is crucial to ensure we register upstream OAuth providers
// in the MAS database
let config = SyncConfig::extract(figment)?;
let config = SyncConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let clock = SystemClock::default();
let encrypter = config.secrets.encrypter().await?;
@@ -213,7 +214,8 @@ impl Options {
Subcommand::Migrate { dry_run } => {
let provider_id_mappings: HashMap<String, Uuid> = {
let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(figment)?;
let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
mas_oauth2
.providers
@@ -252,7 +254,8 @@ impl Options {
let occasional_progress_logger_task =
tokio::spawn(occasional_progress_logger(progress.clone()));
let mas_matrix = MatrixConfig::extract(figment)?;
let mas_matrix =
MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
syn2mas::migrate(
reader,
writer,

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::process::ExitCode;
@@ -37,13 +37,20 @@ impl Options {
SC::Check => {
let _span = info_span!("cli.templates.check").entered();
let template_config = TemplatesConfig::extract_or_default(figment)?;
let branding_config = BrandingConfig::extract_or_default(figment)?;
let matrix_config = MatrixConfig::extract(figment)?;
let experimental_config = ExperimentalConfig::extract_or_default(figment)?;
let password_config = PasswordsConfig::extract_or_default(figment)?;
let account_config = AccountConfig::extract_or_default(figment)?;
let captcha_config = CaptchaConfig::extract_or_default(figment)?;
let template_config = TemplatesConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let branding_config = BrandingConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let matrix_config =
MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
let experimental_config = ExperimentalConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let password_config = PasswordsConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let account_config = AccountConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let captcha_config = CaptchaConfig::extract_or_default(figment)
.map_err(anyhow::Error::from_boxed)?;
let clock = SystemClock::default();
// XXX: we should disallow SeedableRng::from_entropy

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{process::ExitCode, time::Duration};
@@ -10,6 +10,7 @@ use clap::Parser;
use figment::Figment;
use mas_config::{AppConfig, ConfigurationSection};
use mas_router::UrlBuilder;
use mas_storage::SystemClock;
use mas_storage_pg::PgRepositoryFactory;
use tracing::{info, info_span};
@@ -28,7 +29,7 @@ impl Options {
pub async fn run(self, figment: &Figment) -> anyhow::Result<ExitCode> {
let shutdown = LifecycleManager::new()?;
let span = info_span!("cli.worker.init").entered();
let config = AppConfig::extract(figment)?;
let config = AppConfig::extract(figment).map_err(anyhow::Error::from_boxed)?;
// Connect to the database
info!("Connecting to the database");
@@ -63,8 +64,9 @@ impl Options {
drop(config);
info!("Starting task scheduler");
mas_tasks::init(
mas_tasks::init_and_run(
PgRepositoryFactory::new(pool.clone()),
SystemClock::default(),
&mailer,
conn,
url_builder,

View File

@@ -1,7 +1,7 @@
// Copyright 2024, 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{process::ExitCode, time::Duration};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
#![allow(clippy::module_name_repetitions)]
@@ -115,8 +115,9 @@ async fn try_main() -> anyhow::Result<ExitCode> {
// Load the base configuration files
let figment = opts.figment();
let telemetry_config =
TelemetryConfig::extract_or_default(&figment).context("Failed to load telemetry config")?;
let telemetry_config = TelemetryConfig::extract_or_default(&figment)
.map_err(anyhow::Error::from_boxed)
.context("Failed to load telemetry config")?;
// Setup Sentry
let sentry = sentry::init((
@@ -127,8 +128,6 @@ async fn try_main() -> anyhow::Result<ExitCode> {
release: Some(VERSION.into()),
sample_rate: telemetry_config.sentry.sample_rate.unwrap_or(1.0),
traces_sample_rate: telemetry_config.sentry.traces_sample_rate.unwrap_or(0.0),
auto_session_tracking: true,
session_mode: sentry::SessionMode::Request,
..Default::default()
},
));

View File

@@ -1,8 +1,8 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener, ToSocketAddrs},
@@ -205,7 +205,6 @@ async fn log_response_middleware(
response
}
#[allow(clippy::too_many_lines)]
pub fn build_router(
state: AppState,
resources: &[HttpResource],
@@ -269,7 +268,7 @@ pub fn build_router(
}
mas_config::HttpResource::OAuth => router.merge(mas_handlers::api_router::<AppState>()),
mas_config::HttpResource::Compat => {
router.merge(mas_handlers::compat_router::<AppState>())
router.merge(mas_handlers::compat_router::<AppState>(templates.clone()))
}
mas_config::HttpResource::AdminApi => {
let (_, api_router) = mas_handlers::admin_api_router::<AppState>();
@@ -333,7 +332,7 @@ pub fn build_router(
// which is the other way around compared to `tower::ServiceBuilder`.
// So even if the Sentry docs has an example that does
// 'NewSentryHttpLayer then SentryHttpLayer', we must do the opposite.
.layer(SentryHttpLayer::with_transaction())
.layer(SentryHttpLayer::new().enable_transaction())
.layer(NewSentryLayer::new_from_top())
.with_state(state)
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//! Utilities to synchronize the configuration file with the database.
@@ -37,6 +37,19 @@ fn map_import_action(
}
}
fn map_import_on_conflict(
config: mas_config::UpstreamOAuth2OnConflict,
) -> mas_data_model::UpstreamOAuthProviderOnConflict {
match config {
mas_config::UpstreamOAuth2OnConflict::Add => {
mas_data_model::UpstreamOAuthProviderOnConflict::Add
}
mas_config::UpstreamOAuth2OnConflict::Fail => {
mas_data_model::UpstreamOAuthProviderOnConflict::Fail
}
}
}
fn map_claims_imports(
config: &mas_config::UpstreamOAuth2ClaimsImports,
) -> mas_data_model::UpstreamOAuthProviderClaimsImports {
@@ -44,9 +57,10 @@ fn map_claims_imports(
subject: mas_data_model::UpstreamOAuthProviderSubjectPreference {
template: config.subject.template.clone(),
},
localpart: mas_data_model::UpstreamOAuthProviderImportPreference {
localpart: mas_data_model::UpstreamOAuthProviderLocalpartPreference {
action: map_import_action(config.localpart.action),
template: config.localpart.template.clone(),
on_conflict: map_import_on_conflict(config.localpart.on_conflict),
},
displayname: mas_data_model::UpstreamOAuthProviderImportPreference {
action: map_import_action(config.displayname.action),
@@ -276,6 +290,18 @@ pub async fn config_sync(
}
};
let on_backchannel_logout = match provider.on_backchannel_logout {
mas_config::UpstreamOAuth2OnBackchannelLogout::DoNothing => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::DoNothing
}
mas_config::UpstreamOAuth2OnBackchannelLogout::LogoutBrowserOnly => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::LogoutBrowserOnly
}
mas_config::UpstreamOAuth2OnBackchannelLogout::LogoutAll => {
mas_data_model::UpstreamOAuthProviderOnBackchannelLogout::LogoutAll
}
};
repo.upstream_oauth_provider()
.upsert(
clock,
@@ -306,6 +332,7 @@ pub async fn config_sync(
.collect(),
forward_login_hint: provider.forward_login_hint,
ui_order,
on_backchannel_logout,
},
)
.await?;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
mod tokio;

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use opentelemetry::KeyValue;
use tokio::runtime::RuntimeMetrics;
@@ -9,7 +9,6 @@ use tokio::runtime::RuntimeMetrics;
use super::METER;
/// Install metrics for the tokio runtime.
#[allow(clippy::too_many_lines)]
pub fn observe(metrics: RuntimeMetrics) {
{
let metrics = metrics.clone();

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{sync::Arc, time::Duration};
@@ -17,7 +17,7 @@ use mas_data_model::{SessionExpirationConfig, SiteConfig};
use mas_email::{MailTransport, Mailer};
use mas_handlers::passwords::PasswordManager;
use mas_matrix::{HomeserverConnection, ReadOnlyHomeserverConnection};
use mas_matrix_synapse::SynapseConnection;
use mas_matrix_synapse::{LegacySynapseConnection, SynapseConnection};
use mas_policy::PolicyFactory;
use mas_router::UrlBuilder;
use mas_storage::{BoxRepositoryFactory, RepositoryAccess, RepositoryFactory};
@@ -469,14 +469,22 @@ pub fn homeserver_connection_from_config(
http_client: reqwest::Client,
) -> Arc<dyn HomeserverConnection> {
match config.kind {
HomeserverKind::Synapse => Arc::new(SynapseConnection::new(
HomeserverKind::Synapse | HomeserverKind::SynapseLegacy => {
Arc::new(LegacySynapseConnection::new(
config.homeserver.clone(),
config.endpoint.clone(),
config.secret.clone(),
http_client,
))
}
HomeserverKind::SynapseModern => Arc::new(SynapseConnection::new(
config.homeserver.clone(),
config.endpoint.clone(),
config.secret.clone(),
http_client,
)),
HomeserverKind::SynapseReadOnly => {
let connection = SynapseConnection::new(
let connection = LegacySynapseConnection::new(
config.homeserver.clone(),
config.endpoint.clone(),
config.secret.clone(),

View File

@@ -1,3 +1,8 @@
# 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.
[package]
name = "mas-config"
version.workspace = true

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::r#gen::SchemaSettings;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
#![deny(missing_docs, rustdoc::missing_crate_level_docs)]
#![allow(clippy::module_name_repetitions)]

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//! Useful JSON Schema definitions

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::Error};
@@ -51,7 +51,10 @@ impl CaptchaConfig {
impl ConfigurationSection for CaptchaConfig {
const PATH: Option<&'static str> = Some("captcha");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let metadata = figment.find_metadata(Self::PATH.unwrap());
let error_on_field = |mut error: figment::error::Error, field: &'static str| {
@@ -67,11 +70,11 @@ impl ConfigurationSection for CaptchaConfig {
if let Some(CaptchaServiceKind::RecaptchaV2) = self.service {
if self.site_key.is_none() {
return Err(missing_field("site_key"));
return Err(missing_field("site_key").into());
}
if self.secret_key.is_none() {
return Err(missing_field("secret_key"));
return Err(missing_field("secret_key").into());
}
}

View File

@@ -1,12 +1,11 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::ops::Deref;
use figment::Figment;
use mas_iana::oauth::OAuthClientAuthenticationMethod;
use mas_jose::jwk::PublicJsonWebKeySet;
use schemars::JsonSchema;
@@ -104,7 +103,7 @@ pub struct ClientConfig {
}
impl ClientConfig {
fn validate(&self) -> Result<(), figment::error::Error> {
fn validate(&self) -> Result<(), Box<figment::error::Error>> {
let auth_method = self.client_auth_method;
match self.client_auth_method {
ClientAuthMethodConfig::PrivateKeyJwt => {
@@ -112,20 +111,20 @@ impl ClientConfig {
let error = figment::error::Error::custom(
"jwks or jwks_uri is required for private_key_jwt",
);
return Err(error.with_path("client_auth_method"));
return Err(Box::new(error.with_path("client_auth_method")));
}
if self.jwks.is_some() && self.jwks_uri.is_some() {
let error =
figment::error::Error::custom("jwks and jwks_uri are mutually exclusive");
return Err(error.with_path("jwks"));
return Err(Box::new(error.with_path("jwks")));
}
if self.client_secret.is_some() {
let error = figment::error::Error::custom(
"client_secret is not allowed with private_key_jwt",
);
return Err(error.with_path("client_secret"));
return Err(Box::new(error.with_path("client_secret")));
}
}
@@ -136,21 +135,21 @@ impl ClientConfig {
let error = figment::error::Error::custom(format!(
"client_secret is required for {auth_method}"
));
return Err(error.with_path("client_auth_method"));
return Err(Box::new(error.with_path("client_auth_method")));
}
if self.jwks.is_some() {
let error = figment::error::Error::custom(format!(
"jwks is not allowed with {auth_method}"
));
return Err(error.with_path("jwks"));
return Err(Box::new(error.with_path("jwks")));
}
if self.jwks_uri.is_some() {
let error = figment::error::Error::custom(format!(
"jwks_uri is not allowed with {auth_method}"
));
return Err(error.with_path("jwks_uri"));
return Err(Box::new(error.with_path("jwks_uri")));
}
}
@@ -159,21 +158,21 @@ impl ClientConfig {
let error = figment::error::Error::custom(
"client_secret is not allowed with none authentication method",
);
return Err(error.with_path("client_secret"));
return Err(Box::new(error.with_path("client_secret")));
}
if self.jwks.is_some() {
let error = figment::error::Error::custom(
"jwks is not allowed with none authentication method",
);
return Err(error);
return Err(Box::new(error));
}
if self.jwks_uri.is_some() {
let error = figment::error::Error::custom(
"jwks_uri is not allowed with none authentication method",
);
return Err(error);
return Err(Box::new(error));
}
}
}
@@ -232,7 +231,10 @@ impl IntoIterator for ClientsConfig {
impl ConfigurationSection for ClientsConfig {
const PATH: Option<&'static str> = Some("clients");
fn validate(&self, figment: &Figment) -> Result<(), figment::error::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
for (index, client) in self.0.iter().enumerate() {
client.validate().map_err(|mut err| {
// Save the error location information in the error

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{num::NonZeroU32, time::Duration};
@@ -222,13 +222,16 @@ pub struct DatabaseConfig {
impl ConfigurationSection for DatabaseConfig {
const PATH: Option<&'static str> = Some("database");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let metadata = figment.find_metadata(Self::PATH.unwrap());
let annotate = |mut error: figment::Error| {
error.metadata = metadata.cloned();
error.profile = Some(figment::Profile::Default);
error.path = vec![Self::PATH.unwrap().to_owned()];
Err(error)
error
};
// Check that the user did not specify both `uri` and the split options at the
@@ -241,37 +244,41 @@ impl ConfigurationSection for DatabaseConfig {
|| self.database.is_some();
if self.uri.is_some() && has_split_options {
return annotate(figment::error::Error::from(
return Err(annotate(figment::error::Error::from(
"uri must not be specified if host, port, socket, username, password, or database are specified".to_owned(),
));
)).into());
}
if self.ssl_ca.is_some() && self.ssl_ca_file.is_some() {
return annotate(figment::error::Error::from(
return Err(annotate(figment::error::Error::from(
"ssl_ca must not be specified if ssl_ca_file is specified".to_owned(),
));
))
.into());
}
if self.ssl_certificate.is_some() && self.ssl_certificate_file.is_some() {
return annotate(figment::error::Error::from(
return Err(annotate(figment::error::Error::from(
"ssl_certificate must not be specified if ssl_certificate_file is specified"
.to_owned(),
));
))
.into());
}
if self.ssl_key.is_some() && self.ssl_key_file.is_some() {
return annotate(figment::error::Error::from(
return Err(annotate(figment::error::Error::from(
"ssl_key must not be specified if ssl_key_file is specified".to_owned(),
));
))
.into());
}
if (self.ssl_key.is_some() || self.ssl_key_file.is_some())
^ (self.ssl_certificate.is_some() || self.ssl_certificate_file.is_some())
{
return annotate(figment::error::Error::from(
return Err(annotate(figment::error::Error::from(
"both a ssl_certificate and a ssl_key must be set at the same time or none of them"
.to_owned(),
));
))
.into());
}
Ok(())

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
#![allow(deprecated)]
@@ -175,7 +175,10 @@ impl Default for EmailConfig {
impl ConfigurationSection for EmailConfig {
const PATH: Option<&'static str> = Some("email");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let metadata = figment.find_metadata(Self::PATH.unwrap());
let error_on_field = |mut error: figment::error::Error, field: &'static str| {
@@ -201,29 +204,29 @@ impl ConfigurationSection for EmailConfig {
EmailTransportKind::Smtp => {
if let Err(e) = Mailbox::from_str(&self.from) {
return Err(error_on_field(figment::error::Error::custom(e), "from"));
return Err(error_on_field(figment::error::Error::custom(e), "from").into());
}
if let Err(e) = Mailbox::from_str(&self.reply_to) {
return Err(error_on_field(figment::error::Error::custom(e), "reply_to"));
return Err(error_on_field(figment::error::Error::custom(e), "reply_to").into());
}
match (self.username.is_some(), self.password.is_some()) {
(true, true) | (false, false) => {}
(true, false) => {
return Err(missing_field("password"));
return Err(missing_field("password").into());
}
(false, true) => {
return Err(missing_field("username"));
return Err(missing_field("username").into());
}
}
if self.mode.is_none() {
return Err(missing_field("mode"));
return Err(missing_field("mode").into());
}
if self.hostname.is_none() {
return Err(missing_field("hostname"));
return Err(missing_field("hostname").into());
}
if self.command.is_some() {
@@ -239,7 +242,8 @@ impl ConfigurationSection for EmailConfig {
"username",
"password",
],
));
)
.into());
}
}
@@ -247,35 +251,35 @@ impl ConfigurationSection for EmailConfig {
let expected_fields = &["from", "reply_to", "transport", "command"];
if let Err(e) = Mailbox::from_str(&self.from) {
return Err(error_on_field(figment::error::Error::custom(e), "from"));
return Err(error_on_field(figment::error::Error::custom(e), "from").into());
}
if let Err(e) = Mailbox::from_str(&self.reply_to) {
return Err(error_on_field(figment::error::Error::custom(e), "reply_to"));
return Err(error_on_field(figment::error::Error::custom(e), "reply_to").into());
}
if self.command.is_none() {
return Err(missing_field("command"));
return Err(missing_field("command").into());
}
if self.mode.is_some() {
return Err(unexpected_field("mode", expected_fields));
return Err(unexpected_field("mode", expected_fields).into());
}
if self.hostname.is_some() {
return Err(unexpected_field("hostname", expected_fields));
return Err(unexpected_field("hostname", expected_fields).into());
}
if self.port.is_some() {
return Err(unexpected_field("port", expected_fields));
return Err(unexpected_field("port", expected_fields).into());
}
if self.username.is_some() {
return Err(unexpected_field("username", expected_fields));
return Err(unexpected_field("username", expected_fields).into());
}
if self.password.is_some() {
return Err(unexpected_field("password", expected_fields));
return Err(unexpected_field("password", expected_fields).into());
}
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use chrono::Duration;
use schemars::JsonSchema;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
#![allow(deprecated)]
@@ -412,7 +412,10 @@ impl Default for HttpConfig {
impl ConfigurationSection for HttpConfig {
const PATH: Option<&'static str> = Some("http");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
for (index, listener) in self.listeners.iter().enumerate() {
let annotate = |mut error: figment::Error| {
error.metadata = figment
@@ -424,49 +427,57 @@ impl ConfigurationSection for HttpConfig {
"listeners".to_owned(),
index.to_string(),
];
Err(error)
error
};
if listener.resources.is_empty() {
return annotate(figment::Error::from("listener has no resources".to_owned()));
return Err(
annotate(figment::Error::from("listener has no resources".to_owned())).into(),
);
}
if listener.binds.is_empty() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"listener does not bind to any address".to_owned(),
));
))
.into());
}
if let Some(tls_config) = &listener.tls {
if tls_config.certificate.is_some() && tls_config.certificate_file.is_some() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"Only one of `certificate` or `certificate_file` can be set at a time"
.to_owned(),
));
))
.into());
}
if tls_config.certificate.is_none() && tls_config.certificate_file.is_none() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"TLS configuration is missing a certificate".to_owned(),
));
))
.into());
}
if tls_config.key.is_some() && tls_config.key_file.is_some() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"Only one of `key` or `key_file` can be set at a time".to_owned(),
));
))
.into());
}
if tls_config.key.is_none() && tls_config.key_file.is_none() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"TLS configuration is missing a private key".to_owned(),
));
))
.into());
}
if tls_config.password.is_some() && tls_config.password_file.is_some() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"Only one of `password` or `password_file` can be set at a time".to_owned(),
));
))
.into());
}
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use rand::{
Rng,
@@ -27,15 +27,25 @@ fn default_endpoint() -> Url {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum HomeserverKind {
/// Homeserver is Synapse
/// Homeserver is Synapse, using the legacy API
///
/// This will switch to using the modern API in a few releases.
#[default]
Synapse,
/// Homeserver is Synapse, in read-only mode
/// Homeserver is Synapse, using the legacy API, in read-only mode
///
/// This is meant for testing rolling out Matrix Authentication Service with
/// no risk of writing data to the homeserver.
///
/// This will switch to using the modern API in a few releases.
SynapseReadOnly,
/// Homeserver is Synapse, using the legacy API,
SynapseLegacy,
/// Homeserver is Synapse, with the modern API available
SynapseModern,
}
/// Configuration related to the Matrix homeserver

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use rand::Rng;
use schemars::JsonSchema;
@@ -52,7 +52,9 @@ pub use self::{
upstream_oauth2::{
ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
EmailImportPreference as UpstreamOAuth2EmailImportPreference,
ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
ImportAction as UpstreamOAuth2ImportAction,
OnBackchannelLogout as UpstreamOAuth2OnBackchannelLogout,
OnConflict as UpstreamOAuth2OnConflict, PkceMethod as UpstreamOAuth2PkceMethod,
Provider as UpstreamOAuth2Provider, ResponseMode as UpstreamOAuth2ResponseMode,
TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
},
@@ -128,7 +130,10 @@ pub struct RootConfig {
}
impl ConfigurationSection for RootConfig {
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
self.clients.validate(figment)?;
self.http.validate(figment)?;
self.database.validate(figment)?;
@@ -247,7 +252,10 @@ pub struct AppConfig {
}
impl ConfigurationSection for AppConfig {
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
self.http.validate(figment)?;
self.database.validate(figment)?;
self.templates.validate(figment)?;
@@ -283,7 +291,10 @@ pub struct SyncConfig {
}
impl ConfigurationSection for SyncConfig {
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
self.database.validate(figment)?;
self.secrets.validate(figment)?;
self.clients.validate(figment)?;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::cmp::Reverse;
@@ -72,12 +72,15 @@ impl Default for PasswordsConfig {
impl ConfigurationSection for PasswordsConfig {
const PATH: Option<&'static str> = Some("passwords");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let annotate = |mut error: figment::Error| {
error.metadata = figment.find_metadata(Self::PATH.unwrap()).cloned();
error.profile = Some(figment::Profile::Default);
error.path = vec![Self::PATH.unwrap().to_owned()];
Err(error)
error
};
if !self.enabled {
@@ -86,16 +89,18 @@ impl ConfigurationSection for PasswordsConfig {
}
if self.schemes.is_empty() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"Requires at least one password scheme in the config".to_owned(),
));
))
.into());
}
for scheme in &self.schemes {
if scheme.secret.is_some() && scheme.secret_file.is_some() {
return annotate(figment::Error::from(
return Err(annotate(figment::Error::from(
"Cannot specify both `secret` and `secret_file`".to_owned(),
));
))
.into());
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use camino::Utf8PathBuf;
use schemars::JsonSchema;

View File

@@ -1,8 +1,8 @@
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{num::NonZeroU32, time::Duration};
@@ -117,7 +117,10 @@ pub struct RateLimiterConfiguration {
impl ConfigurationSection for RateLimitingConfig {
const PATH: Option<&'static str> = Some("rate_limiting");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let metadata = figment.find_metadata(Self::PATH.unwrap());
let error_on_field = |mut error: figment::error::Error, field: &'static str| {
@@ -154,25 +157,21 @@ impl ConfigurationSection for RateLimitingConfig {
};
if let Some(error) = error_on_limiter(&self.account_recovery.per_ip) {
return Err(error_on_nested_field(error, "account_recovery", "per_ip"));
return Err(error_on_nested_field(error, "account_recovery", "per_ip").into());
}
if let Some(error) = error_on_limiter(&self.account_recovery.per_address) {
return Err(error_on_nested_field(
error,
"account_recovery",
"per_address",
));
return Err(error_on_nested_field(error, "account_recovery", "per_address").into());
}
if let Some(error) = error_on_limiter(&self.registration) {
return Err(error_on_field(error, "registration"));
return Err(error_on_field(error, "registration").into());
}
if let Some(error) = error_on_limiter(&self.login.per_ip) {
return Err(error_on_nested_field(error, "login", "per_ip"));
return Err(error_on_nested_field(error, "login", "per_ip").into());
}
if let Some(error) = error_on_limiter(&self.login.per_account) {
return Err(error_on_nested_field(error, "login", "per_account"));
return Err(error_on_nested_field(error, "login", "per_account").into());
}
Ok(())

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::borrow::Cow;
@@ -149,10 +149,10 @@ impl KeyConfig {
/// Returns the password in case any is provided.
///
/// If `password_file` was given, the password is read from that file.
async fn password(&self) -> anyhow::Result<Option<Cow<String>>> {
async fn password(&self) -> anyhow::Result<Option<Cow<[u8]>>> {
Ok(match &self.password {
Some(Password::File(path)) => Some(Cow::Owned(tokio::fs::read_to_string(path).await?)),
Some(Password::Value(password)) => Some(Cow::Borrowed(password)),
Some(Password::File(path)) => Some(Cow::Owned(tokio::fs::read(path).await?)),
Some(Password::Value(password)) => Some(Cow::Borrowed(password.as_bytes())),
None => None,
})
}
@@ -160,10 +160,10 @@ impl KeyConfig {
/// Returns the key.
///
/// If `key_file` was given, the key is read from that file.
async fn key(&self) -> anyhow::Result<Cow<String>> {
async fn key(&self) -> anyhow::Result<Cow<[u8]>> {
Ok(match &self.key {
Key::File(path) => Cow::Owned(tokio::fs::read_to_string(path).await?),
Key::Value(key) => Cow::Borrowed(key),
Key::File(path) => Cow::Owned(tokio::fs::read(path).await?),
Key::Value(key) => Cow::Borrowed(key.as_bytes()),
})
}
@@ -174,8 +174,8 @@ impl KeyConfig {
let (key, password) = try_join(self.key(), self.password()).await?;
let private_key = match password {
Some(password) => PrivateKey::load_encrypted(key.as_bytes(), password.as_bytes())?,
None => PrivateKey::load(key.as_bytes())?,
Some(password) => PrivateKey::load_encrypted(&key, password)?,
None => PrivateKey::load(&key)?,
};
Ok(JsonWebKey::new(private_key)
@@ -303,6 +303,7 @@ impl ConfigurationSection for SecretsConfig {
}
impl SecretsConfig {
#[expect(clippy::similar_names, reason = "Key type names are very similar")]
#[tracing::instrument(skip_all)]
pub(crate) async fn generate<R>(mut rng: R) -> anyhow::Result<Self>
where
@@ -347,7 +348,7 @@ impl SecretsConfig {
let ec_p384_key = task::spawn_blocking(move || {
let _entered = span.enter();
let ret = PrivateKey::generate_ec_p384(key_rng);
info!("Done generating EC P-256 key");
info!("Done generating EC P-384 key");
ret
})
.await

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::Error as _};
@@ -194,13 +194,17 @@ impl TelemetryConfig {
impl ConfigurationSection for TelemetryConfig {
const PATH: Option<&'static str> = Some("telemetry");
fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
_figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
if let Some(sample_rate) = self.sentry.sample_rate {
if !(0.0..=1.0).contains(&sample_rate) {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.sample_rate"));
.with_path("sentry.sample_rate")
.into());
}
}
@@ -209,7 +213,8 @@ impl ConfigurationSection for TelemetryConfig {
return Err(figment::error::Error::custom(
"Sentry sample rate must be between 0.0 and 1.0",
)
.with_path("sentry.traces_sample_rate"));
.with_path("sentry.traces_sample_rate")
.into());
}
}
@@ -218,7 +223,8 @@ impl ConfigurationSection for TelemetryConfig {
return Err(figment::error::Error::custom(
"Tracing sample rate must be between 0.0 and 1.0",
)
.with_path("tracing.sample_rate"));
.with_path("tracing.sample_rate")
.into());
}
}

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use camino::Utf8PathBuf;
use schemars::JsonSchema;

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::collections::BTreeMap;
@@ -33,7 +33,10 @@ impl UpstreamOAuth2Config {
impl ConfigurationSection for UpstreamOAuth2Config {
const PATH: Option<&'static str> = Some("upstream_oauth2");
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
fn validate(
&self,
figment: &figment::Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
for (index, provider) in self.providers.iter().enumerate() {
let annotate = |mut error: figment::Error| {
error.metadata = figment
@@ -45,15 +48,16 @@ impl ConfigurationSection for UpstreamOAuth2Config {
"providers".to_owned(),
index.to_string(),
];
Err(error)
error
};
if !matches!(provider.discovery_mode, DiscoveryMode::Disabled)
&& provider.issuer.is_none()
{
return annotate(figment::Error::custom(
return Err(annotate(figment::Error::custom(
"The `issuer` field is required when discovery is enabled",
));
))
.into());
}
match provider.token_endpoint_auth_method {
@@ -61,16 +65,16 @@ impl ConfigurationSection for UpstreamOAuth2Config {
| TokenAuthMethod::PrivateKeyJwt
| TokenAuthMethod::SignInWithApple => {
if provider.client_secret.is_some() {
return annotate(figment::Error::custom(
return Err(annotate(figment::Error::custom(
"Unexpected field `client_secret` for the selected authentication method",
));
)).into());
}
}
TokenAuthMethod::ClientSecretBasic
| TokenAuthMethod::ClientSecretPost
| TokenAuthMethod::ClientSecretJwt => {
if provider.client_secret.is_none() {
return annotate(figment::Error::missing_field("client_secret"));
return Err(annotate(figment::Error::missing_field("client_secret")).into());
}
}
}
@@ -81,16 +85,17 @@ impl ConfigurationSection for UpstreamOAuth2Config {
| TokenAuthMethod::ClientSecretPost
| TokenAuthMethod::SignInWithApple => {
if provider.token_endpoint_auth_signing_alg.is_some() {
return annotate(figment::Error::custom(
return Err(annotate(figment::Error::custom(
"Unexpected field `token_endpoint_auth_signing_alg` for the selected authentication method",
));
)).into());
}
}
TokenAuthMethod::ClientSecretJwt | TokenAuthMethod::PrivateKeyJwt => {
if provider.token_endpoint_auth_signing_alg.is_none() {
return annotate(figment::Error::missing_field(
return Err(annotate(figment::Error::missing_field(
"token_endpoint_auth_signing_alg",
));
))
.into());
}
}
}
@@ -98,18 +103,32 @@ impl ConfigurationSection for UpstreamOAuth2Config {
match provider.token_endpoint_auth_method {
TokenAuthMethod::SignInWithApple => {
if provider.sign_in_with_apple.is_none() {
return annotate(figment::Error::missing_field("sign_in_with_apple"));
return Err(
annotate(figment::Error::missing_field("sign_in_with_apple")).into(),
);
}
}
_ => {
if provider.sign_in_with_apple.is_some() {
return annotate(figment::Error::custom(
return Err(annotate(figment::Error::custom(
"Unexpected field `sign_in_with_apple` for the selected authentication method",
));
)).into());
}
}
}
if matches!(
provider.claims_imports.localpart.on_conflict,
OnConflict::Add
) && !matches!(
provider.claims_imports.localpart.action,
ImportAction::Force | ImportAction::Require
) {
return Err(annotate(figment::Error::custom(
"The field `action` must be either `force` or `require` when `on_conflict` is set to `add`",
)).into());
}
}
Ok(())
@@ -183,6 +202,26 @@ impl ImportAction {
}
}
/// How to handle an existing localpart claim
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum OnConflict {
/// Fails the sso login on conflict
#[default]
Fail,
/// Adds the oauth identity link, regardless of whether there is an existing
/// link or not
Add,
}
impl OnConflict {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default(&self) -> bool {
matches!(self, OnConflict::Fail)
}
}
/// What should be done for the subject attribute
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct SubjectImportPreference {
@@ -211,6 +250,10 @@ pub struct LocalpartImportPreference {
/// If not provided, the default template is `{{ user.preferred_username }}`
#[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
/// How to handle conflicts on the claim, default value is `Fail`
#[serde(default, skip_serializing_if = "OnConflict::is_default")]
pub on_conflict: OnConflict,
}
impl LocalpartImportPreference {
@@ -408,6 +451,29 @@ fn is_default_scope(scope: &str) -> bool {
scope == default_scope()
}
/// What to do when receiving an OIDC Backchannel logout request.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum OnBackchannelLogout {
/// Do nothing
#[default]
DoNothing,
/// Only log out the MAS 'browser session' started by this OIDC session
LogoutBrowserOnly,
/// Log out all sessions started by this OIDC session, including MAS
/// 'browser sessions' and client sessions
LogoutAll,
}
impl OnBackchannelLogout {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default(&self) -> bool {
matches!(self, OnBackchannelLogout::DoNothing)
}
}
/// Configuration for one upstream OAuth 2 provider.
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
@@ -583,4 +649,10 @@ pub struct Provider {
/// Defaults to `false`.
#[serde(default)]
pub forward_login_hint: bool,
/// What to do when receiving an OIDC Backchannel logout request.
///
/// Defaults to "do_nothing".
#[serde(default, skip_serializing_if = "OnBackchannelLogout::is_default")]
pub on_backchannel_logout: OnBackchannelLogout,
}

View File

@@ -1,10 +1,10 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use figment::{Figment, error::Error as FigmentError};
use figment::Figment;
use serde::de::DeserializeOwned;
/// Trait implemented by all configuration section to help loading specific part
@@ -18,7 +18,10 @@ pub trait ConfigurationSection: Sized + DeserializeOwned {
/// # Errors
///
/// Returns an error if the configuration is invalid
fn validate(&self, _figment: &Figment) -> Result<(), FigmentError> {
fn validate(
&self,
_figment: &Figment,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
Ok(())
}
@@ -27,7 +30,9 @@ pub trait ConfigurationSection: Sized + DeserializeOwned {
/// # Errors
///
/// Returns an error if the configuration could not be loaded
fn extract(figment: &Figment) -> Result<Self, FigmentError> {
fn extract(
figment: &Figment,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync + 'static>> {
let this: Self = if let Some(path) = Self::PATH {
figment.extract_inner(path)?
} else {
@@ -49,7 +54,9 @@ pub trait ConfigurationSectionExt: ConfigurationSection + Default {
/// # Errors
///
/// Returns an error if the configuration section is invalid.
fn extract_or_default(figment: &Figment) -> Result<Self, figment::Error> {
fn extract_or_default(
figment: &Figment,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync + 'static>> {
let this: Self = if let Some(path) = Self::PATH {
// If the configuration section is not present, we return the default value
if !figment.contains(path) {

View File

@@ -1,3 +1,8 @@
# 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.
[package]
name = "mas-context"
version.workspace = true

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use console::{Color, Style};
use opentelemetry::{

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{
pin::Pin,

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::borrow::Cow;

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
mod fmt;
mod future;

View File

@@ -1,7 +1,7 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use std::{
borrow::Cow,

View File

@@ -1,3 +1,8 @@
# 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.
[package]
name = "mas-data-model"
version.workspace = true

View File

@@ -1,8 +1,8 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
use mas_data_model::UserAgent;

Some files were not shown because too many files have changed in this diff Show More