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,
8 utils::{BoolExt, future::OptionFutureExt, time::timepoint_has_passed},
9};
10use tuwunel_service::users::device::{RefreshToken, 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 match services
32 .users
33 .classify_refresh_token(&refresh_token_claim)
34 .await
35 {
36 | RefreshToken::Current { user_id, device_id, expires_at } => {
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 services.users.locked_check(&user_id).await?;
56
57 let refresh_token = Some(generate_refresh_token());
58 let (access_token, expires_in_ms) = services.users.generate_access_token(true);
59
60 services
61 .users
62 .set_access_token(
63 &user_id,
64 &device_id,
65 &access_token,
66 expires_in_ms,
67 refresh_token.as_deref(),
68 )
69 .await?;
70
71 debug_info!(?user_id, ?device_id, ?expires_in_ms, "refreshed their access_token",);
72
73 Ok(Response {
74 access_token,
75 refresh_token,
76 expires_in_ms,
77 })
78 },
79
80 | RefreshToken::Replayed { user_id, device_id, current, grace } if grace => {
81 services.users.locked_check(&user_id).await?;
82
83 let (access_token, expires_in_ms) = services.users.generate_access_token(true);
86
87 services
88 .users
89 .set_access_token(&user_id, &device_id, &access_token, expires_in_ms, None)
90 .await?;
91
92 Ok(Response {
93 access_token,
94 refresh_token: Some(current),
95 expires_in_ms,
96 })
97 },
98
99 | RefreshToken::Replayed { user_id, device_id, .. } => {
100 let revoke = services.server.config.refresh_token_reuse_revoke;
101 debug_info!(?user_id, ?device_id, revoke, "refresh token reused after rotation");
102
103 if revoke {
104 services
105 .users
106 .remove_device(&user_id, &device_id)
107 .await;
108 }
109
110 Err(Error::BadRequest(
111 ErrorKind::UnknownToken(UnknownTokenErrorData { soft_logout: !revoke }),
112 "Refresh token has already been used.",
113 ))
114 },
115
116 | RefreshToken::Unknown => Err!(Request(Forbidden("Refresh token is unrecognized."))),
117 }
118}