Skip to main content

quorum_public/utility/
jwt.rs

1//! JWT token generation and verification.
2//!
3//! This module provides functions to create access and refresh tokens with appropriate claims,
4//! and to validate tokens against the configured JWT secret. All configuration values are loaded
5//! from environment variables and cached in the `Config` singleton.
6
7use chrono::{Duration, Utc};
8use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
9use quorum_core::utility::config::Config;
10use serde::{Deserialize, Serialize};
11use std::error::Error;
12
13#[derive(Debug, Serialize, Deserialize)]
14pub struct Claims {
15    pub sub: String,
16    pub username: String,
17    pub exp: i64,
18    pub iat: i64,
19    pub token_type: String,
20}
21
22/// Generates a new access token for a user.
23///
24/// Creates a JWT access token with the user's ID and username. The token is signed using
25/// the configured `JWT_SECRET` and expires after `JWT_ACCESS_MINUTES` minutes.
26/// Access tokens are short-lived and used for API request authentication.
27///
28/// # Arguments
29/// * `user_id` - The unique identifier of the user.
30/// * `username` - The username of the user.
31///
32/// # Returns
33/// * `Ok(String)` - The encoded JWT token string.
34/// * `Err(Box<dyn Error>)` - If token encoding fails or config is not initialized.
35///
36/// # Errors
37/// Returns an error if:
38/// - JWT encoding fails (invalid secret or configuration)
39/// - The configured JWT secret is missing or invalid
40/// - Config has not been initialized via `Config::load()`
41///
42/// # Example
43/// ```rust,no_run
44/// use crate::utility::jwt;
45///
46/// let token = jwt::generate_access_token("user123", "john_doe")
47///     .expect("Failed to generate token");
48/// println!("Access token: {}", token);
49/// ```
50pub fn generate_access_token(user_id: &str, username: &str) -> Result<String, Box<dyn Error>> {
51    let config = Config::get();
52    let now = Utc::now();
53    let expiry = now + Duration::minutes(config.jwt_access_minutes);
54
55    let claims = Claims {
56        sub: user_id.to_string(),
57        username: username.to_string(),
58        exp: expiry.timestamp(),
59        iat: now.timestamp(),
60        token_type: "access".to_string(),
61    };
62
63    let token = encode(
64        &Header::default(),
65        &claims,
66        &EncodingKey::from_secret(config.jwt_secret.as_ref()),
67    )?;
68
69    Ok(token)
70}
71
72/// Generates a new refresh token for a user.
73///
74/// Creates a JWT refresh token with the user's ID and username. The token is signed using
75/// the configured `JWT_SECRET` and expires after `JWT_REFRESH_DAYS` days.
76/// Refresh tokens are long-lived and used to obtain new access tokens without re-authentication.
77///
78/// # Arguments
79/// * `user_id` - The unique identifier of the user.
80/// * `username` - The username of the user.
81///
82/// # Returns
83/// * `Ok(String)` - The encoded JWT token string.
84/// * `Err(Box<dyn Error>)` - If token encoding fails or config is not initialized.
85///
86/// # Errors
87/// Returns an error if:
88/// - JWT encoding fails (invalid secret or configuration)
89/// - The configured JWT secret is missing or invalid
90/// - Config has not been initialized via `Config::load()`
91///
92/// # Example
93/// ```rust,no_run
94/// use crate::utility::jwt;
95///
96/// let token = jwt::generate_refresh_token("user123", "john_doe")
97///     .expect("Failed to generate token");
98/// println!("Refresh token: {}", token);
99/// ```
100pub fn generate_refresh_token(user_id: &str, username: &str) -> Result<String, Box<dyn Error>> {
101    let config = Config::get();
102    let now = Utc::now();
103    let expiry = now + Duration::days(config.jwt_refresh_days);
104
105    let claims = Claims {
106        sub: user_id.to_string(),
107        username: username.to_string(),
108        exp: expiry.timestamp(),
109        iat: now.timestamp(),
110        token_type: "refresh".to_string(),
111    };
112
113    let token = encode(
114        &Header::default(),
115        &claims,
116        &EncodingKey::from_secret(config.jwt_secret.as_ref()),
117    )?;
118
119    Ok(token)
120}
121
122/// Verifies and decodes a JWT token.
123///
124/// Validates the token signature against the configured `JWT_SECRET` and checks expiration.
125/// Returns the decoded claims if the token is valid, allowing the caller to access the user ID
126/// and other claim data without re-parsing.
127///
128/// # Arguments
129/// * `token` - The encoded JWT token string to verify.
130///
131/// # Returns
132/// * `Ok(Claims)` - The decoded claims if the token is valid.
133/// * `Err(Box<dyn Error>)` - If token verification fails.
134///
135/// # Errors
136/// Returns an error if:
137/// - The token is malformed or cannot be decoded
138/// - The signature does not match the configured secret
139/// - The token has expired
140/// - Required claims are missing
141/// - Config has not been initialized via `Config::load()`
142///
143/// # Example
144/// ```rust,no_run
145/// use crate::utility::jwt;
146///
147/// let token = "eyJ0eXAiOiJKV1QiLCJhbGc..."; // Valid JWT
148/// match jwt::verify_token(token) {
149///     Ok(claims) => println!("Authenticated user: {}", claims.sub),
150///     Err(_) => println!("Invalid or expired token"),
151/// }
152/// ```
153pub fn verify_token(token: &str) -> Result<Claims, Box<dyn Error + Send + Sync>> {
154    let config = Config::get();
155    let data = decode::<Claims>(
156        token,
157        &DecodingKey::from_secret(config.jwt_secret.as_ref()),
158        &Validation::default(),
159    )?;
160
161    Ok(data.claims)
162}