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.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/// # `POST /_matrix/client/v3/login`
104///
105/// Authenticates the user and returns an access token it can use in subsequent
106/// requests.
107///
108/// - The user needs to authenticate using their password (or if enabled using a
109///   json web token)
110/// - If `device_id` is known: issues an additional access token for that device
111/// - If `device_id` is unknown: creates a new device
112/// - Returns access token that is associated with the user and device
113///
114/// Note: You can use [`GET
115/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
116/// supported login types.
117#[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	// Validate login method
124	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	// Generate a new token for the device
142	let (access_token, expires_in) = services
143		.users
144		.generate_access_token(body.body.refresh_token);
145
146	// Generate a new refresh_token if requested by client
147	let refresh_token = expires_in.is_some().then(generate_refresh_token);
148
149	// Determine if device_id was provided and exists in the db for this user
150	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	// send client well-known if specified so the client knows to reconfigure itself
188	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}