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