tuwunel_api_client/session/
mod.rs1mod appservice;
2#[cfg(feature = "jwt_login")]
3pub(crate) mod jwt;
4mod ldap;
5mod logout;
6mod password;
7mod refresh;
8mod sso;
9mod token;
10
11use axum::extract::State;
12use ruma::api::client::session::{
13 get_login_types::{
14 self,
15 v3::{
16 ApplicationServiceLoginType, IdentityProvider, JwtLoginType, LoginType,
17 PasswordLoginType, SsoLoginType, TokenLoginType,
18 },
19 },
20 login::{
21 self,
22 v3::{DiscoveryInfo, HomeserverInfo, LoginInfo},
23 },
24};
25use tuwunel_core::{Err, Result, err, info, utils::stream::ReadyExt};
26use tuwunel_service::users::device::generate_refresh_token;
27
28use self::{
29 ldap::ldap_login,
30 password::{handle_login as password_handle_login, password_login},
31};
32pub(crate) use self::{
33 logout::{logout_all_route, logout_route},
34 refresh::refresh_token_route,
35 sso::{
36 sso_callback_route, sso_complete_js_route, sso_css_route, sso_fallback_route,
37 sso_login_route, sso_login_with_provider_route,
38 },
39 token::login_token_route,
40};
41use super::TOKEN_LENGTH;
42use crate::{ClientIp, Ruma};
43
44#[tracing::instrument(skip_all, fields(%client), name = "login")]
49pub(crate) async fn get_login_types_route(
50 State(services): State<crate::State>,
51 ClientIp(client): ClientIp,
52 _body: Ruma<get_login_types::v3::Request>,
53) -> Result<get_login_types::v3::Response> {
54 let get_login_token = services
55 .config
56 .authentication
57 .login
58 .via_existing_session;
59
60 let list_idps = services.config.lists_identity_providers();
61
62 let identity_providers: Option<Vec<_>> = list_idps.then(|| {
63 services
64 .config
65 .identity_provider
66 .values()
67 .map(|config| IdentityProvider {
68 id: config.id().to_owned(),
69 brand: Some(config.brand.as_str().into()),
70 icon: config.icon.clone(),
71 name: config.display_name().to_owned(),
72 })
73 .collect()
74 });
75
76 let show_sso = identity_providers
77 .as_ref()
78 .is_none_or(|providers| !providers.is_empty());
79
80 let appservice = Some(LoginType::ApplicationService(ApplicationServiceLoginType::default()));
81
82 let token = Some(LoginType::Token(TokenLoginType { get_login_token }));
83
84 let password = services
85 .config
86 .authentication
87 .login
88 .with_password
89 .then(|| LoginType::Password(PasswordLoginType::default()));
90
91 let sso = show_sso.then(|| {
92 let oauth_aware_preferred = services
93 .config
94 .authentication
95 .login
96 .oidc_aware_preferred;
97
98 LoginType::Sso(SsoLoginType {
99 identity_providers: identity_providers.unwrap_or_default(),
100 oauth_aware_preferred,
101 })
102 });
103
104 let has_jwt = cfg!(feature = "jwt_login") && services.config.jwt.enable;
105 let jwt = has_jwt.then(|| LoginType::Jwt(JwtLoginType::default()));
106
107 let flows = [appservice, token, password, sso, jwt]
108 .into_iter()
109 .flatten()
110 .collect();
111
112 Ok(get_login_types::v3::Response { flows })
113}
114
115#[tracing::instrument(
130 name = "login",
131 level = "debug",
132 skip_all,
133 fields(client = %client_ip),
134)]
135pub(crate) async fn login_route(
136 State(services): State<crate::State>,
137 ClientIp(client_ip): ClientIp,
138 body: Ruma<login::v3::Request>,
139) -> Result<login::v3::Response> {
140 let user_id = match &body.login_info {
141 | LoginInfo::Token(info) => token::handle_login(&services, &body, info).await?,
142 | LoginInfo::Password(info) if services.config.authentication.login.with_password => {
143 let client = body
144 .rate_limit_ip
145 .map_err(|message| err!("{message}"))?;
146
147 password_handle_login(&services, &body, info, client).await?
148 },
149 #[cfg(feature = "jwt_login")]
150 | LoginInfo::Jwt(info) if services.config.jwt.enable =>
151 jwt::handle_login(&services, &body, info).await?,
152 | LoginInfo::ApplicationService(info) =>
153 appservice::handle_login(&services, &body, info)?,
154 | _ => {
155 return Err!(Request(Unknown(debug_warn!("Invalid or unsupported login type"))));
156 },
157 };
158
159 if !matches!(body.login_info, LoginInfo::ApplicationService(_)) {
161 services.users.deactivated_check(&user_id).await?;
162 }
163
164 services.users.locked_check(&user_id).await?;
165
166 let expiring =
168 matches!(body.login_info, LoginInfo::Jwt(_)) && services.config.jwt.expire_access_token;
169
170 let refreshable = body.body.refresh_token && !expiring;
171 let (access_token, expires_in) = services
172 .users
173 .generate_access_token(expiring || refreshable);
174
175 let refresh_token = refreshable.then(generate_refresh_token);
176
177 let device_id = if let Some(device_id) = &body.device_id
178 && services
179 .users
180 .all_device_ids(&user_id)
181 .ready_any(|existing_device_id| existing_device_id == device_id)
182 .await
183 {
184 services
185 .users
186 .set_access_token(
187 &user_id,
188 device_id,
189 &access_token,
190 expires_in,
191 refresh_token.as_deref(),
192 )
193 .await?;
194
195 device_id.clone()
196 } else {
197 services
198 .users
199 .create_device(
200 &user_id,
201 body.device_id.as_deref(),
202 (Some(&access_token), expires_in),
203 refresh_token.as_deref(),
204 body.initial_device_display_name.as_deref(),
205 Some(client_ip),
206 )
207 .await?
208 };
209
210 info!("{user_id} logged in");
211
212 let home_server = services.server.name.clone().into();
213
214 let well_known: Option<DiscoveryInfo> = services
216 .config
217 .well_known
218 .client
219 .as_ref()
220 .map(ToString::to_string)
221 .map(HomeserverInfo::new)
222 .map(DiscoveryInfo::new);
223
224 #[expect(deprecated)]
225 Ok(login::v3::Response {
226 user_id,
227 access_token,
228 device_id,
229 home_server,
230 well_known,
231 expires_in,
232 refresh_token,
233 })
234}