Skip to main content

tuwunel_api_mas/
mod.rs

1mod allow_cross_signing_reset;
2mod delete_device;
3mod delete_user;
4mod is_localpart_available;
5mod provision_user;
6mod query_user;
7mod reactivate_user;
8/// Provides the Matrix Authentication Service route builder.
9///
10/// It groups the account and device integration endpoints.
11pub mod routes;
12mod set_displayname;
13mod sync_devices;
14mod unset_displayname;
15mod update_device_display_name;
16mod upsert_device;
17
18use axum::{RequestPartsExt, extract::FromRequestParts};
19use axum_extra::{
20	TypedHeader,
21	headers::{Authorization, authorization::Bearer},
22};
23use http::request::Parts;
24use log as _;
25use ruma::{OwnedUserId, UserId};
26use tuwunel_api::router::{Ruma, State};
27use tuwunel_core::{Err, Error, Result, err, mod_ctor, mod_dtor};
28
29pub(crate) use self::{
30	allow_cross_signing_reset::allow_cross_signing_reset_route,
31	delete_device::delete_device_route, delete_user::delete_user_route,
32	is_localpart_available::is_localpart_available_route, provision_user::provision_user_route,
33	query_user::query_user_route, reactivate_user::reactivate_user_route,
34	set_displayname::set_displayname_route, sync_devices::sync_devices_route,
35	unset_displayname::unset_displayname_route,
36	update_device_display_name::update_device_display_name_route,
37	upsert_device::upsert_device_route,
38};
39
40/// Asserts a request originates from MAS by matching its bearer token against
41/// the configured shared secret, the equivalent of Synapse's
42/// `assert_request_is_from_mas`. Rejects with 403 on mismatch or no secret.
43pub(crate) struct Mas;
44
45impl FromRequestParts<State> for Mas {
46	type Rejection = Error;
47
48	async fn from_request_parts(
49		parts: &mut Parts,
50		services: &State,
51	) -> Result<Self, Self::Rejection> {
52		let secret = services
53			.config
54			.authentication
55			.mas_secret
56			.as_deref()
57			.filter(|secret| !secret.is_empty());
58
59		let bearer = parts
60			.extract::<TypedHeader<Authorization<Bearer>>>()
61			.await
62			.ok();
63
64		let token = bearer
65			.as_ref()
66			.map(|TypedHeader(Authorization(bearer))| bearer.token());
67
68		match (secret, token) {
69			| (Some(secret), Some(token)) if secret == token => Ok(Self),
70			| _ => Err!(Request(Forbidden("This endpoint may only be called by MAS"))),
71		}
72	}
73}
74
75/// Parses a MAS `localpart` into a local user id, rejecting a malformed one
76/// with `400`.
77pub(crate) fn local_user(services: State, localpart: &str) -> Result<OwnedUserId> {
78	UserId::parse_with_server_name(localpart, services.globals.server_name())
79		.map_err(|_| err!(Request(InvalidParam("Invalid localpart"))))
80}
81
82/// Resolves a MAS `localpart` to an existing local user, rejecting an absent
83/// one with `404`.
84pub(crate) async fn existing_user(services: State, localpart: &str) -> Result<OwnedUserId> {
85	let user_id = local_user(services, localpart)?;
86
87	services
88		.users
89		.exists(&user_id)
90		.await
91		.then_some(user_id)
92		.ok_or_else(|| err!(Request(NotFound("User does not exist"))))
93}
94
95mod_ctor! {}
96mod_dtor! {}