Skip to main content

tuwunel_api/client/admin/tokens/
update.rs

1use std::time::{Duration, SystemTime};
2
3use axum::extract::State;
4use ruma::{JsOption, MilliSecondsSinceUnixEpoch, UInt};
5use synapse_admin_api::registration_tokens::update::v1 as update;
6use tuwunel_core::{Err, Result, utils::time::timepoint_from_epoch};
7use tuwunel_service::registration_tokens::{TokenExpires, TokenInfo};
8
9use super::database_token_response;
10use crate::{Ruma, client::admin::require_admin};
11
12/// # `PUT /_synapse/admin/v1/registration_tokens/{token}`
13///
14/// Only the cap and expiry are updatable; an omitted field is left unchanged
15/// and an explicit `null` clears it.
16pub(crate) async fn admin_update_token_route(
17	State(services): State<crate::State>,
18	body: Ruma<update::Request>,
19) -> Result<update::Response> {
20	require_admin(&services, body.sender_user()).await?;
21
22	let token = &body.token;
23
24	let info = services
25		.registration_tokens
26		.get_token_info(token)
27		.await?;
28
29	let info = match info {
30		| TokenInfo::Database(info) => info,
31		| TokenInfo::Config =>
32			return Err!(Request(Forbidden("Tokens set in the config file can't be updated"))),
33	};
34
35	let max_uses = apply_uses(body.uses_allowed, info.expires.max_uses);
36	let max_age = apply_age(body.expiry_time, info.expires.max_age)?;
37	let expires = TokenExpires { max_uses, max_age };
38
39	let info = services
40		.registration_tokens
41		.update_token(token, expires)
42		.await?;
43
44	Ok(update::Response {
45		token: database_token_response(token.clone(), &info),
46	})
47}
48
49/// Fold the tri-state `uses_allowed` over the stored cap: an undefined field
50/// leaves it unchanged, an explicit `null` clears it.
51fn apply_uses(uses_allowed: JsOption<UInt>, current: Option<u64>) -> Option<u64> {
52	match uses_allowed.into_nested_option() {
53		| None => current,
54		| Some(cap) => cap.map(Into::into),
55	}
56}
57
58/// Fold the tri-state `expiry_time` over the stored expiry, converting a set
59/// millisecond timestamp into a `SystemTime`.
60fn apply_age(
61	expiry_time: JsOption<MilliSecondsSinceUnixEpoch>,
62	current: Option<SystemTime>,
63) -> Result<Option<SystemTime>> {
64	match expiry_time.into_nested_option() {
65		| None => Ok(current),
66		| Some(None) => Ok(None),
67		| Some(Some(ms)) => timepoint_from_epoch(Duration::from_millis(ms.0.into())).map(Some),
68	}
69}