Skip to main content

ferrox_security/
dual_token.rs

1use crate::{PasetoAuth, AuthPayload};
2use ferrox_errors::AppError;
3use secrecy::Secret;
4use serde::{Deserialize, Serialize};
5
6#[derive(Serialize, Deserialize)]
7pub struct DualTokens {
8    pub access_token: String,
9    pub refresh_token: String,
10}
11
12pub struct DualTokenManager {
13    paseto: PasetoAuth,
14}
15
16impl DualTokenManager {
17    pub fn new(secret_key: Secret<String>) -> Self {
18        Self {
19            paseto: PasetoAuth::new(secret_key).unwrap(),
20        }
21    }
22
23    /// Generates both an Access Token (short-lived) and a Refresh Token (long-lived).
24    pub fn generate_tokens(&self, payload: &AuthPayload) -> Result<DualTokens, AppError> {
25        // Access Token: 15 minutes
26        let access_token = self.paseto.generate_token(payload, time::Duration::minutes(15))?;
27        
28        // Refresh Token: Usually stored in DB/Redis with a longer expiration and UUID.
29        // For boilerplate, we generate a cryptographically strong random hex.
30        // In a real implementation, you would save this to Redis/DB to allow revocation.
31        use rand::RngCore;
32        let mut key = [0u8; 32];
33        rand::thread_rng().fill_bytes(&mut key);
34        let refresh_token = hex::encode(key);
35
36        Ok(DualTokens {
37            access_token,
38            refresh_token,
39        })
40    }
41
42    /// Verifies the Access token. If it fails, the frontend should use the Refresh Token
43    /// on a dedicated endpoint to get a new pair.
44    pub fn verify_access_token(&self, token: &str) -> Result<String, AppError> {
45        let claims = self.paseto.validate_token(token)?;
46        Ok(claims.user_id)
47    }
48}