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