tuwunel_api/client/session/
password.rs1use futures::{FutureExt, TryFutureExt};
2use ruma::{
3 OwnedUserId, UserId,
4 api::client::{
5 session::login::v3::{Password, Request},
6 uiaa,
7 },
8};
9use tuwunel_core::{Err, Result, debug_error, err, utils::hash::verify_password};
10use tuwunel_service::Services;
11
12use super::ldap_login;
13use crate::Ruma;
14
15pub(super) async fn handle_login(
16 services: &Services,
17 body: &Ruma<Request>,
18 info: &Password,
19) -> Result<OwnedUserId> {
20 #[expect(deprecated)]
21 let Password { identifier, password, user, .. } = info;
22
23 let user_id = if let Some(uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier {
24 user: user_id,
25 ..
26 })) = identifier
27 {
28 UserId::parse_with_server_name(user_id, &services.config.server_name)
29 } else if let Some(user) = user {
30 UserId::parse_with_server_name(user, &services.config.server_name)
31 } else {
32 return Err!(Request(Unknown(debug_warn!(
33 ?body.login_info,
34 "Valid identifier or username was not provided (invalid or unsupported login type?)"
35 ))));
36 }
37 .map_err(|e| err!(Request(InvalidUsername(warn!("Username is invalid: {e}")))))?;
38
39 let lowercased_user_id = UserId::parse_with_server_name(
40 user_id.localpart().to_lowercase(),
41 &services.config.server_name,
42 )?;
43
44 let user_is_remote = !services.globals.user_is_local(&user_id)
45 || !services
46 .globals
47 .user_is_local(&lowercased_user_id);
48
49 if user_is_remote {
50 return Err!(Request(Unknown("User ID does not belong to this homeserver")));
51 }
52
53 if cfg!(feature = "ldap") && services.config.ldap.enable {
54 ldap_login(services, &user_id, &lowercased_user_id, password)
55 .boxed()
56 .await
57 } else {
58 password_login(services, &user_id, &lowercased_user_id, password).await
59 }
60}
61
62#[tracing::instrument(skip_all, fields(%user_id), name = "password")]
66pub(super) async fn password_login(
67 services: &Services,
68 user_id: &UserId,
69 lowercased_user_id: &UserId,
70 password: &str,
71) -> Result<OwnedUserId> {
72 let reservation = services
74 .login_ratelimit
75 .reserve_login_attempt(user_id)?;
76
77 let wrong = || err!(Request(Forbidden("Wrong username or password.")));
78
79 if services
82 .users
83 .origin(user_id)
84 .await
85 .is_ok_and(|origin| origin != "password")
86 {
87 services
88 .login_ratelimit
89 .refund_login_attempt(reservation)?;
90
91 return Err!(Request(Forbidden("Account does not permit password login.")));
92 }
93
94 let (hash, user_id) = services
97 .users
98 .password_hash(user_id)
99 .map_ok(|hash| (hash, user_id))
100 .or_else(|_| {
101 services
102 .users
103 .password_hash(lowercased_user_id)
104 .map_ok(|hash| (hash, lowercased_user_id))
105 })
106 .map_err(|_| wrong())
107 .await?;
108
109 if hash.is_empty() {
110 services
111 .login_ratelimit
112 .refund_login_attempt(reservation)?;
113
114 return Err!(Request(UserDeactivated("The user has been deactivated")));
115 }
116
117 verify_password(password, &hash)
118 .inspect_err(|e| debug_error!("{e}"))
119 .map_err(|_| wrong())?;
120
121 services
122 .login_ratelimit
123 .record_login(reservation)?;
124
125 Ok(user_id.to_owned())
126}