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}