1use std::{collections::HashMap, hash::BuildHasher};
2
3use subtle::ConstantTimeEq;
4
5use crate::{AuthError, AuthMethod, Principal};
6
7#[tracing::instrument(
17 level = "debug",
18 skip_all,
19 fields(mechanism = "PLAIN", user = %user),
20 err
21)]
22pub fn verify_plain<S: BuildHasher>(
25 creds: &HashMap<String, String, S>,
26 user: &str,
27 password: &[u8],
28) -> Result<Principal, AuthError> {
29 let Some(expected) = creds.get(user) else {
30 return Err(AuthError::UnknownUser);
31 };
32 if expected.as_bytes().ct_eq(password).unwrap_u8() == 1 {
33 Ok(Principal {
34 name: user.to_owned(),
35 auth_method: AuthMethod::SaslPlain,
36 groups: vec![],
37 })
38 } else {
39 Err(AuthError::BadPassword)
40 }
41}
42
43#[cfg(test)]
44mod tests {
45
46 use super::*;
47
48 fn creds() -> HashMap<String, String> {
49 let mut m = HashMap::new();
50 m.insert("alice".into(), "wonderland".into());
51 m
52 }
53
54 #[test]
55 fn correct_creds_pass() {
56 let p = verify_plain(&creds(), "alice", b"wonderland").unwrap();
57 assert2::assert!(p.name.as_str() == "alice");
58 assert2::assert!(p.auth_method == AuthMethod::SaslPlain);
59 }
60
61 #[test]
62 fn wrong_password_fails() {
63 assert2::assert!(
64 verify_plain(&creds(), "alice", b"hunter2") == Err(AuthError::BadPassword)
65 );
66 }
67
68 #[test]
69 fn unknown_user_fails() {
70 assert2::assert!(verify_plain(&creds(), "bob", b"anything") == Err(AuthError::UnknownUser));
71 }
72}