feat(auth): implement core service layer (password, JWT, auth, user CRUD)
- error.rs: AuthError with proper HTTP status mapping - service/password.rs: Argon2 hash/verify with tests - service/token_service.rs: JWT sign/validate, token DB storage with SHA-256 hash - service/auth_service.rs: login/refresh/logout flows with event publishing - service/user_service.rs: user CRUD with soft delete and tenant isolation - Added sha2 dependency to workspace for token hashing
This commit is contained in:
56
crates/erp-auth/src/service/password.rs
Normal file
56
crates/erp-auth/src/service/password.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
|
||||
use crate::error::{AuthError, AuthResult};
|
||||
|
||||
/// Hash a plaintext password using Argon2 with a random salt.
|
||||
///
|
||||
/// Returns a PHC-format string suitable for database storage.
|
||||
pub fn hash_password(plain: &str) -> AuthResult<String> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
let hash = argon2
|
||||
.hash_password(plain.as_bytes(), &salt)
|
||||
.map_err(|e| AuthError::HashError(e.to_string()))?;
|
||||
Ok(hash.to_string())
|
||||
}
|
||||
|
||||
/// Verify a plaintext password against a stored PHC-format hash.
|
||||
///
|
||||
/// Returns `Ok(true)` if the password matches, `Ok(false)` if not.
|
||||
pub fn verify_password(plain: &str, hash: &str) -> AuthResult<bool> {
|
||||
let parsed = PasswordHash::new(hash).map_err(|e| AuthError::HashError(e.to_string()))?;
|
||||
Ok(Argon2::default()
|
||||
.verify_password(plain.as_bytes(), &parsed)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hash_and_verify() {
|
||||
let hash = hash_password("test123").unwrap();
|
||||
assert!(
|
||||
verify_password("test123", &hash).unwrap(),
|
||||
"Correct password should verify"
|
||||
);
|
||||
assert!(
|
||||
!verify_password("wrong", &hash).unwrap(),
|
||||
"Wrong password should not verify"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_is_unique() {
|
||||
let hash1 = hash_password("same_password").unwrap();
|
||||
let hash2 = hash_password("same_password").unwrap();
|
||||
assert_ne!(
|
||||
hash1, hash2,
|
||||
"Two hashes of the same password should differ (different salts)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user