Skip to main content

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