tuwunel_api/client/session/
refresh.rs1use 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#[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 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}