Architecture
The service is meant to be easily embeddable, with only a dependency to a database. It is also meant to stay lightweight in terms of resource usage and easily scalable horizontally.
Scope and goals
The Matrix Authentication Service has been created to support the migration of Matrix to an OpenID Connect (OIDC) based architecture as per MSC3861.
It is not intended to be a general purpose Identity Provider (IdP) and instead focuses on the specific needs of Matrix.
Furthermore, it is only intended that it would speak OIDC for authentication and not other protocols. Instead, if you want to connect to an upstream SAML, CAS or LDAP backend then you need to pair MAS with a separate service (such as Dex or Keycloak) which does that translation for you.
Whilst it only supports use with Synapse today, we hope that other homeservers will become supported in future.
If you need some other feature that MAS doesn't support (such as TOTP or WebAuthn), then you should consider pairing MAS with another IdP that does support the features you need.
Workspace and crate split
The whole repository is a Cargo Workspace that includes multiple crates under the /crates
directory.
This includes:
mas-cli
: Command line utility, main entry pointmas-config
: Configuration parsing and loadingmas-data-model
: Models of objects that live in the database, regardless of the storage backendmas-email
: High-level email sending abstractionmas-handlers
: Main HTTP application logicmas-iana
: Auto-generated enums from IANA registriesmas-iana-codegen
: Code generator for themas-iana
cratemas-jose
: JWT/JWS/JWE/JWK abstractionmas-static-files
: Frontend static files (CSS/JS). Includes some frontend toolingmas-storage
: Abstraction of the storage backendsmas-storage-pg
: Storage backend implementation for a PostgreSQL databasemas-tasks
: Asynchronous task runner and scheduleroauth2-types
: Useful structures and types to deal with OAuth 2.0/OpenID Connect endpoints. This might end up published as a standalone library as it can be useful in other contexts.
Important crates
The project makes use of a few important crates.
Async runtime: tokio
Tokio is the async runtime used by the project. The choice of runtime does not have much impact on most of the code.
It has an impact when:
- spawning asynchronous work (as in "not awaiting on it immediately")
- running CPU-intensive tasks. They should be ran in a blocking context using
tokio::task::spawn_blocking
. This includes password hashing and other crypto operations. - when dealing with shared memory, e.g. mutexes, rwlocks, etc.
Logging: tracing
Logging is handled through the tracing
crate.
It provides a way to emit structured log messages at various levels.
#![allow(unused)] fn main() { use tracing::{info, debug}; info!("Logging some things"); debug!(user = "john", "Structured stuff"); }
tracing
also provides ways to create spans to better understand where a logging message comes from.
In the future, it will help building OpenTelemetry-compatible distributed traces to help with debugging.
tracing
is becoming the standard to log things in Rust.
By itself it will do nothing unless a subscriber is installed to -for example- log the events to the console.
The CLI installs tracing-subcriber
on startup to log in the console.
It looks for a RUST_LOG
environment variable to determine what event should be logged.
Error management: thiserror
/ anyhow
thiserror
helps defining custom error types.
This is especially useful for errors that should be handled in a specific way, while being able to augment underlying errors with additional context.
anyhow
helps dealing with chains of errors.
It allows for quickly adding additional context around an error while it is being propagated.
Both crates work well together and complement each other.
Database interactions: sqlx
Interactions with the database are done through sqlx
, an async, pure-Rust SQL library with compile-time check of queries.
It also handles schema migrations.
Templates: tera
Tera was chosen as template engine for its simplicity as well as its ability to load templates at runtime. The builtin templates are embedded in the final binary through some macro magic.
The downside of Tera compared to compile-time template engines is the possibility of runtime crashes. This can however be somewhat mitigated with unit tests.
Crates from RustCrypto
The RustCrypto team offer high quality, independent crates for dealing with cryptography. The whole project is highly modular and APIs are coherent between crates.