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_fallback_route, sso_login_route, sso_login_with_provider_route,
33 },
34 token::login_token_route,
35};
36use super::TOKEN_LENGTH;
37use crate::{ClientIp, Ruma};
38
39#[tracing::instrument(skip_all, fields(%client), name = "login")]
44pub(crate) async fn get_login_types_route(
45 State(services): State<crate::State>,
46 ClientIp(client): ClientIp,
47 _body: Ruma<get_login_types::v3::Request>,
48) -> Result<get_login_types::v3::Response> {
49 let get_login_token = services.config.login_via_existing_session;
50
51 let list_idps = !services.config.sso_custom_providers_page && !services.config.single_sso;
52
53 let identity_providers: Vec<_> = services
54 .config
55 .identity_provider
56 .values()
57 .filter(|_| list_idps)
58 .cloned()
59 .map(|config| IdentityProvider {
60 id: config.id().to_owned(),
61 brand: Some(config.brand.clone().into()),
62 icon: config.icon,
63 name: config.name.unwrap_or(config.brand),
64 })
65 .collect();
66
67 let flows = [
68 LoginType::ApplicationService(ApplicationServiceLoginType::default()),
69 LoginType::Jwt(JwtLoginType::default()),
70 LoginType::Password(PasswordLoginType::default()),
71 LoginType::Token(TokenLoginType { get_login_token }),
72 LoginType::Sso(SsoLoginType {
73 identity_providers,
74 oauth_aware_preferred: services.config.oidc_aware_preferred,
75 }),
76 ];
77
78 Ok(get_login_types::v3::Response {
79 flows: flows
80 .into_iter()
81 .filter(|login_type| match login_type {
82 | LoginType::Sso(SsoLoginType { identity_providers, .. })
83 if list_idps && identity_providers.is_empty() =>
84 false,
85 | LoginType::Password(_) => services.config.login_with_password,
86 | LoginType::Jwt(_) => services.config.jwt.enable,
87 | _ => true,
88 })
89 .collect(),
90 })
91}
92
93#[tracing::instrument(name = "login", skip_all, fields(%client, ?body.login_info))]
108pub(crate) async fn login_route(
109 State(services): State<crate::State>,
110 ClientIp(client): ClientIp,
111 body: Ruma<login::v3::Request>,
112) -> Result<login::v3::Response> {
113 let user_id = match &body.login_info {
115 | LoginInfo::Password(info) => password::handle_login(&services, &body, info).await?,
116 | LoginInfo::Token(info) => token::handle_login(&services, &body, info).await?,
117 | LoginInfo::Jwt(info) => jwt::handle_login(&services, &body, info).await?,
118 | LoginInfo::ApplicationService(info) =>
119 appservice::handle_login(&services, &body, info)?,
120 | _ => {
121 return Err!(Request(Unknown(debug_warn!(
122 ?body.login_info,
123 ?body.json_body,
124 "Invalid or unsupported login type",
125 ))));
126 },
127 };
128
129 let (access_token, expires_in) = services
131 .users
132 .generate_access_token(body.body.refresh_token);
133
134 let refresh_token = expires_in.is_some().then(generate_refresh_token);
136
137 let device_id = if let Some(device_id) = &body.device_id
139 && services
140 .users
141 .all_device_ids(&user_id)
142 .ready_any(|v| v == device_id)
143 .await
144 {
145 services
146 .users
147 .set_access_token(
148 &user_id,
149 device_id,
150 &access_token,
151 expires_in,
152 refresh_token.as_deref(),
153 )
154 .await?;
155
156 device_id.clone()
157 } else {
158 services
159 .users
160 .create_device(
161 &user_id,
162 body.device_id.as_deref(),
163 (Some(&access_token), expires_in),
164 refresh_token.as_deref(),
165 body.initial_device_display_name.as_deref(),
166 Some(client),
167 )
168 .await?
169 };
170
171 info!("{user_id} logged in");
172
173 let home_server = services.server.name.clone().into();
174
175 let well_known: Option<DiscoveryInfo> = services
177 .config
178 .well_known
179 .client
180 .as_ref()
181 .map(ToString::to_string)
182 .map(HomeserverInfo::new)
183 .map(DiscoveryInfo::new);
184
185 #[expect(deprecated)]
186 Ok(login::v3::Response {
187 user_id,
188 access_token,
189 device_id,
190 home_server,
191 well_known,
192 expires_in,
193 refresh_token,
194 })
195}