Skip to main content

tuwunel_api/client/session/
refresh.rs

1use axum::extract::State;
2use ruma::api::{
3	client::session::refresh_token::v3::{Request, Response},
4	error::{ErrorKind, UnknownTokenErrorData},
5};
6use tuwunel_core::{
7	Err, Error, Result, debug_info, err,
8	utils::{BoolExt, future::OptionFutureExt, time::timepoint_has_passed},
9};
10use tuwunel_service::users::device::generate_refresh_token;
11
12use crate::{ClientIp, Ruma};
13
14/// # `POST /_matrix/client/v3/refresh`
15///
16/// Refresh an access token.
17///
18/// <https://spec.matrix.org/v1.15/client-server-api/#post_matrixclientv3refresh>
19#[tracing::instrument(skip_all, fields(%client), name = "refresh_token")]
20pub(crate) async fn refresh_token_route(
21	State(services): State<crate::State>,
22	ClientIp(client): ClientIp,
23	body: Ruma<Request>,
24) -> Result<Response> {
25	let refresh_token_claim = body.body.refresh_token;
26
27	if !refresh_token_claim.starts_with("refresh_") {
28		return Err!(Request(Forbidden("Refresh token is malformed.")));
29	}
30
31	let (user_id, device_id, expires_at) = services
32		.users
33		.find_from_token(&refresh_token_claim)
34		.await
35		.map_err(|e| err!(Request(Forbidden("Refresh token is unrecognized: {e}"))))?;
36
37	if expires_at.is_some_and(timepoint_has_passed) {
38		let hard = services.server.config.refresh_token_hard_logout;
39		hard.then_async(|| services.users.remove_device(&user_id, &device_id))
40			.unwrap_or_else_async(async || {
41				services
42					.users
43					.remove_refresh_token(&user_id, &device_id)
44					.await
45					.ok();
46			})
47			.await;
48
49		return Err(Error::BadRequest(
50			ErrorKind::UnknownToken(UnknownTokenErrorData { soft_logout: !hard }),
51			"Refresh token has expired.",
52		));
53	}
54
55	// New tokens
56	let refresh_token = Some(generate_refresh_token());
57	let (access_token, expires_in_ms) = services.users.generate_access_token(true);
58
59	services
60		.users
61		.set_access_token(
62			&user_id,
63			&device_id,
64			&access_token,
65			expires_in_ms,
66			refresh_token.as_deref(),
67		)
68		.await?;
69
70	debug_info!(?user_id, ?device_id, ?expires_in_ms, "refreshed their access_token",);
71
72	Ok(Response {
73		access_token,
74		refresh_token,
75		expires_in_ms,
76	})
77}