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