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_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/// # `GET /_matrix/client/v3/login`
40///
41/// Get the supported login types of this server. One of these should be used as
42/// the `type` field when logging in.
43#[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/// # `POST /_matrix/client/v3/login`
94///
95/// Authenticates the user and returns an access token it can use in subsequent
96/// requests.
97///
98/// - The user needs to authenticate using their password (or if enabled using a
99///   json web token)
100/// - If `device_id` is known: invalidates old access token of that device
101/// - If `device_id` is unknown: creates a new device
102/// - Returns access token that is associated with the user and device
103///
104/// Note: You can use [`GET
105/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
106/// supported login types.
107#[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	// Validate login method
114	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	// Generate a new token for the device
130	let (access_token, expires_in) = services
131		.users
132		.generate_access_token(body.body.refresh_token);
133
134	// Generate a new refresh_token if requested by client
135	let refresh_token = expires_in.is_some().then(generate_refresh_token);
136
137	// Determine if device_id was provided and exists in the db for this user
138	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	// send client well-known if specified so the client knows to reconfigure itself
176	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}