nxtgauge-backend-rust/crates/auth/src/jwt.rs
Ashwin Kumar 9764a7acdd feat: commit remaining service files, migrations, and model updates
- gateway, companies, customers, job_seekers apps updated
- users config/mod/mail handlers
- auth middleware and jwt crate updates
- db models: user, config, mod updates
- all remaining migrations: portfolio, notifications, reviews, kb, support, coupons, onboarding states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:59:47 +01:00

71 lines
2 KiB
Rust

use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation, Algorithm};
use serde::{Deserialize, Serialize};
/// JWT claims — must match `contracts::auth_middleware::Claims` field-for-field
/// so that tokens generated here can be decoded there.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
pub sub: String, // user_id (UUID string)
pub email: String,
pub roles: Vec<String>,
pub active_role: String,
pub exp: usize,
pub iat: usize,
}
pub struct JwtTokens {
pub access_token: String,
pub refresh_token: String,
}
/// Generate an access token + a random refresh token string.
///
/// The refresh token is NOT a JWT — it is a random opaque string stored
/// (hashed) in the `refresh_tokens` table.
pub fn generate_tokens(
user_id: String,
email: String,
roles: Vec<String>,
active_role: Option<String>,
jwt_secret: &str,
) -> anyhow::Result<JwtTokens> {
let now = Utc::now();
let access_exp = now + Duration::minutes(15);
let active_role = active_role
.or_else(|| roles.first().cloned())
.unwrap_or_default();
let claims = Claims {
sub: user_id,
email,
roles,
active_role,
iat: now.timestamp() as usize,
exp: access_exp.timestamp() as usize,
};
let access_token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(jwt_secret.as_bytes()),
)?;
// Refresh token is an opaque random string stored in DB, not a JWT.
let refresh_token = uuid::Uuid::new_v4().to_string() + &uuid::Uuid::new_v4().to_string();
Ok(JwtTokens {
access_token,
refresh_token: refresh_token.replace('-', ""),
})
}
pub fn verify_access_token(token: &str, jwt_secret: &str) -> anyhow::Result<Claims> {
let token_data = decode::<Claims>(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&Validation::new(Algorithm::HS256),
)?;
Ok(token_data.claims)
}