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