reifydb_auth/method/
password.rs1use std::collections::HashMap;
5
6use argon2::{
7 Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
8 password_hash::Error as PasswordHashError,
9};
10use reifydb_core::interface::auth::{AuthStep, AuthenticationProvider};
11use reifydb_runtime::context::rng::Rng;
12use reifydb_value::{Result, error::Error};
13
14use crate::error::AuthError;
15
16pub struct PasswordProvider;
17
18fn argon2_instance() -> Argon2<'static> {
19 let params = Params::new(19 * 1024, 2, 1, Some(32)).expect("valid Argon2 params");
20 Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
21}
22
23impl AuthenticationProvider for PasswordProvider {
24 fn method(&self) -> &str {
25 "password"
26 }
27
28 fn create(&self, _rng: &Rng, config: &HashMap<String, String>) -> Result<HashMap<String, String>> {
29 let password = config.get("password").ok_or_else(|| Error::from(AuthError::PasswordRequired))?;
30
31 let argon2 = argon2_instance();
32
33 let phc = argon2
34 .hash_password(password.as_bytes())
35 .map_err(|e| {
36 Error::from(AuthError::HashingFailed {
37 reason: e.to_string(),
38 })
39 })?
40 .to_string();
41
42 Ok(HashMap::from([("phc".into(), phc), ("algorithm_version".into(), "1".into())]))
43 }
44
45 fn authenticate(
46 &self,
47 stored: &HashMap<String, String>,
48 credentials: &HashMap<String, String>,
49 ) -> Result<AuthStep> {
50 let credential = credentials.get("password").ok_or_else(|| Error::from(AuthError::PasswordRequired))?;
51
52 let phc_str = stored.get("phc").ok_or_else(|| {
53 Error::from(AuthError::InvalidHash {
54 reason: "missing 'phc' field".to_string(),
55 })
56 })?;
57
58 let parsed_hash = PasswordHash::new(phc_str).map_err(|e| {
59 Error::from(AuthError::InvalidHash {
60 reason: e.to_string(),
61 })
62 })?;
63
64 let argon2 = argon2_instance();
65
66 match argon2.verify_password(credential.as_bytes(), &parsed_hash) {
67 Ok(()) => Ok(AuthStep::Authenticated),
68 Err(PasswordHashError::PasswordInvalid) => Ok(AuthStep::Failed),
69 Err(e) => Err(Error::from(AuthError::VerificationFailed {
70 reason: e.to_string(),
71 })),
72 }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn test_password_create_and_authenticate() {
82 let provider = PasswordProvider;
83 let config = HashMap::from([("password".to_string(), "secret123".to_string())]);
84
85 let stored = provider.create(&Rng::default(), &config).unwrap();
86 assert!(stored.contains_key("phc"));
87 assert!(stored.get("phc").unwrap().starts_with("$argon2id$"));
88 assert_eq!(stored.get("algorithm_version").unwrap(), "1");
89
90 let correct = HashMap::from([("password".to_string(), "secret123".to_string())]);
91 assert_eq!(provider.authenticate(&stored, &correct).unwrap(), AuthStep::Authenticated);
92
93 let wrong = HashMap::from([("password".to_string(), "wrong_password".to_string())]);
94 assert_eq!(provider.authenticate(&stored, &wrong).unwrap(), AuthStep::Failed);
95 }
96
97 #[test]
98 fn test_password_requires_password_field() {
99 let provider = PasswordProvider;
100 let config = HashMap::new();
101 assert!(provider.create(&Rng::default(), &config).is_err());
102 }
103
104 #[test]
105 fn test_authenticate_corrupted_hash() {
106 let provider = PasswordProvider;
107 let stored = HashMap::from([
108 ("phc".into(), "not-a-valid-phc-string".to_string()),
109 ("algorithm_version".into(), "1".into()),
110 ]);
111 let creds = HashMap::from([("password".to_string(), "anything".to_string())]);
112 assert!(provider.authenticate(&stored, &creds).is_err());
113 }
114
115 #[test]
116 fn test_authenticate_missing_phc() {
117 let provider = PasswordProvider;
118 let stored = HashMap::new();
119 let creds = HashMap::from([("password".to_string(), "anything".to_string())]);
120 assert!(provider.authenticate(&stored, &creds).is_err());
121 }
122
123 #[test]
124 fn test_authenticate_missing_password_credential() {
125 let provider = PasswordProvider;
126 let config = HashMap::from([("password".to_string(), "secret123".to_string())]);
127 let stored = provider.create(&Rng::default(), &config).unwrap();
128 let empty_creds = HashMap::new();
129 assert!(provider.authenticate(&stored, &empty_creds).is_err());
130 }
131}