juiceback/
auth.rs

1//! Authentication: Argon2 password hashing and JWT tokens.
2
3use argon2::{
4    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
5    Argon2,
6};
7use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
8use serde::{Deserialize, Serialize};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11/// Claims payload stored inside admin JWT tokens.
12#[derive(Debug, Serialize, Deserialize)]
13pub struct AdminClaims {
14    /// Admin username (subject).
15    pub sub: String,
16    /// Token expiry as a UNIX timestamp.
17    pub exp: usize,
18    /// Token issue time as a UNIX timestamp.
19    pub iat: usize,
20}
21
22/// Hash a plaintext password using Argon2id with a random salt.
23pub fn hash_password(password: &str) -> Result<String, String> {
24    let salt = SaltString::generate(&mut OsRng);
25    let argon2 = Argon2::default();
26    let hash = argon2
27        .hash_password(password.as_bytes(), &salt)
28        .map_err(|e| format!("hashing failed: {}", e))?;
29    Ok(hash.to_string())
30}
31
32/// Verify a plaintext password against an Argon2id hash.
33pub fn verify_password(password: &str, hash: &str) -> Result<bool, String> {
34    let parsed =
35        PasswordHash::new(hash).map_err(|e| format!("invalid hash format: {}", e))?;
36    Ok(Argon2::default()
37        .verify_password(password.as_bytes(), &parsed)
38        .is_ok())
39}
40
41/// Create a signed JWT for the given username, valid for 24 hours.
42pub fn create_jwt(username: &str, secret: &str) -> Result<String, String> {
43    let now = SystemTime::now()
44        .duration_since(UNIX_EPOCH)
45        .map_err(|e| format!("time error: {}", e))?
46        .as_secs() as usize;
47
48    let claims = AdminClaims {
49        sub: username.to_string(),
50        iat: now,
51        exp: now + 86400,
52    };
53
54    encode(
55        &Header::default(),
56        &claims,
57        &EncodingKey::from_secret(secret.as_bytes()),
58    )
59    .map_err(|e| format!("jwt encoding failed: {}", e))
60}
61
62/// Verify a JWT and return its claims.
63pub fn verify_jwt(token: &str, secret: &str) -> Result<AdminClaims, String> {
64    let token_data = decode::<AdminClaims>(
65        token,
66        &DecodingKey::from_secret(secret.as_bytes()),
67        &Validation::default(),
68    )
69    .map_err(|e| format!("jwt verification failed: {}", e))?;
70    Ok(token_data.claims)
71}