1use kellnr_common::account_name::UserName;
2use kellnr_db::{DbProvider, User};
3
4use crate::login_throttle::LoginThrottle;
5
6#[derive(Debug, PartialEq, Eq)]
7pub enum PasswordAuthError {
8 Throttled,
10 Invalid,
12}
13
14pub async fn authenticate_password(
18 db: &dyn DbProvider,
19 throttle: &LoginThrottle,
20 user: &UserName,
21 pwd: &str,
22) -> Result<User, PasswordAuthError> {
23 if !throttle.try_attempt(user.as_str()).await {
24 return Err(PasswordAuthError::Throttled);
25 }
26
27 let user = db
28 .authenticate_user(user, pwd)
29 .await
30 .map_err(|_| PasswordAuthError::Invalid)?;
31 throttle.succeeded(user.name.as_str()).await;
32 Ok(user)
33}
34
35#[cfg(test)]
36mod tests {
37 use std::time::Duration;
38
39 use kellnr_db::UserFlags;
40 use kellnr_test_db::TestDb;
41
42 use super::*;
43
44 fn throttle(max_attempts: u32) -> LoginThrottle {
45 LoginThrottle::new(max_attempts, Duration::from_mins(5))
46 }
47
48 fn user() -> UserName {
49 UserName::new("user").unwrap()
50 }
51
52 async fn user_db() -> TestDb {
54 let db = TestDb::sqlite().await;
55 db.add_user(&user(), "right", UserFlags::default())
56 .await
57 .unwrap();
58 db
59 }
60
61 #[tokio::test]
62 async fn valid_password_authenticates() {
63 let db = user_db().await;
64
65 let result = authenticate_password(&*db, &throttle(3), &user(), "right").await;
66
67 assert_eq!(result.unwrap().name, "user");
68 }
69
70 #[tokio::test]
73 async fn throttles_after_limit_without_checking_password() {
74 let db = user_db().await;
75 let throttle = throttle(2);
76
77 for _ in 0..2 {
78 let result = authenticate_password(&*db, &throttle, &user(), "wrong").await;
79 assert_eq!(result.unwrap_err(), PasswordAuthError::Invalid);
80 }
81 let result = authenticate_password(&*db, &throttle, &user(), "right").await;
82 assert_eq!(result.unwrap_err(), PasswordAuthError::Throttled);
83 }
84
85 #[tokio::test]
86 async fn unknown_user_is_throttled() {
87 let db = user_db().await;
88 let throttle = throttle(1);
89 let ghost = UserName::new("ghost").unwrap();
90
91 let first = authenticate_password(&*db, &throttle, &ghost, "pwd").await;
92 let second = authenticate_password(&*db, &throttle, &ghost, "pwd").await;
93
94 assert_eq!(first.unwrap_err(), PasswordAuthError::Invalid);
95 assert_eq!(second.unwrap_err(), PasswordAuthError::Throttled);
96 }
97
98 #[tokio::test]
99 async fn success_resets_attempts() {
100 let db = user_db().await;
101 let throttle = throttle(2);
102
103 let _ = authenticate_password(&*db, &throttle, &user(), "wrong").await;
104 authenticate_password(&*db, &throttle, &user(), "right")
105 .await
106 .unwrap();
107
108 for _ in 0..2 {
110 let result = authenticate_password(&*db, &throttle, &user(), "wrong").await;
111 assert_eq!(result.unwrap_err(), PasswordAuthError::Invalid);
112 }
113 }
114}