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.sso_custom_providers_page && !services.config.single_sso;
53
54 let identity_providers: Option<Vec<_>> = list_idps.then(|| {
55 services
56 .config
57 .identity_provider
58 .values()
59 .cloned()
60 .map(|config| IdentityProvider {
61 id: config.id().to_owned(),
62 brand: Some(config.brand.clone().into()),
63 icon: config.icon,
64 name: config.name.unwrap_or(config.brand),
65 })
66 .collect()
67 });
68
69 let show_sso = identity_providers
70 .as_ref()
71 .is_none_or(|providers| !providers.is_empty());
72
73 let appservice = Some(LoginType::ApplicationService(ApplicationServiceLoginType::default()));
74
75 let token = Some(LoginType::Token(TokenLoginType { get_login_token }));
76
77 let password = services
78 .config
79 .login_with_password
80 .then(|| LoginType::Password(PasswordLoginType::default()));
81
82 let sso = show_sso.then(|| {
83 LoginType::Sso(SsoLoginType {
84 identity_providers: identity_providers.unwrap_or_default(),
85 oauth_aware_preferred: services.config.oidc_aware_preferred,
86 })
87 });
88
89 let jwt = services
90 .config
91 .jwt
92 .enable
93 .then(|| LoginType::Jwt(JwtLoginType::default()));
94
95 let flows = [appservice, token, password, sso, jwt]
96 .into_iter()
97 .flatten()
98 .collect();
99
100 Ok(get_login_types::v3::Response { flows })
101}
102
103#[tracing::instrument(name = "login", skip_all, fields(%client, ?body.login_info))]
118pub(crate) async fn login_route(
119 State(services): State<crate::State>,
120 ClientIp(client): ClientIp,
121 body: Ruma<login::v3::Request>,
122) -> Result<login::v3::Response> {
123 let user_id = match &body.login_info {
125 | LoginInfo::Password(info) if services.config.login_with_password =>
126 password::handle_login(&services, &body, info).await?,
127 | LoginInfo::Token(info) => token::handle_login(&services, &body, info).await?,
128 | LoginInfo::Jwt(info) if services.config.jwt.enable =>
129 jwt::handle_login(&services, &body, info).await?,
130 | LoginInfo::ApplicationService(info) =>
131 appservice::handle_login(&services, &body, info)?,
132 | _ => {
133 return Err!(Request(Unknown(debug_warn!(
134 ?body.login_info,
135 ?body.json_body,
136 "Invalid or unsupported login type",
137 ))));
138 },
139 };
140
141 let (access_token, expires_in) = services
143 .users
144 .generate_access_token(body.body.refresh_token);
145
146 let refresh_token = expires_in.is_some().then(generate_refresh_token);
148
149 let device_id = if let Some(device_id) = &body.device_id
151 && services
152 .users
153 .all_device_ids(&user_id)
154 .ready_any(|v| v == device_id)
155 .await
156 {
157 services
158 .users
159 .set_access_token(
160 &user_id,
161 device_id,
162 &access_token,
163 expires_in,
164 refresh_token.as_deref(),
165 )
166 .await?;
167
168 device_id.clone()
169 } else {
170 services
171 .users
172 .create_device(
173 &user_id,
174 body.device_id.as_deref(),
175 (Some(&access_token), expires_in),
176 refresh_token.as_deref(),
177 body.initial_device_display_name.as_deref(),
178 Some(client),
179 )
180 .await?
181 };
182
183 info!("{user_id} logged in");
184
185 let home_server = services.server.name.clone().into();
186
187 let well_known: Option<DiscoveryInfo> = services
189 .config
190 .well_known
191 .client
192 .as_ref()
193 .map(ToString::to_string)
194 .map(HomeserverInfo::new)
195 .map(DiscoveryInfo::new);
196
197 #[expect(deprecated)]
198 Ok(login::v3::Response {
199 user_id,
200 access_token,
201 device_id,
202 home_server,
203 well_known,
204 expires_in,
205 refresh_token,
206 })
207}