Skip to main content

tuwunel_api/client/session/
mod.rs

1mod 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/// # `GET /_matrix/client/v3/login`
41///
42/// Get the supported login types of this server. One of these should be used as
43/// the `type` field when logging in.
44#[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/// # `POST /_matrix/client/v3/login`
103///
104/// Authenticates the user and returns an access token it can use in subsequent
105/// requests.
106///
107/// - The user needs to authenticate using their password (or if enabled using a
108///   JSON Web Token)
109/// - If `device_id` is known: issues an additional access token for that device
110/// - If `device_id` is unknown: creates a new device
111/// - Returns access token that is associated with the user and device
112///
113/// Note: You can use [`GET
114/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
115/// supported login types.
116#[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	// Appservice users are often passwordless, which reads as deactivated.
141	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	// Send client well-known information when configured, so the client can reconfigure itself.
191	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}