1use dotenv::dotenv;
2use jsonwebtoken::{
3 decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation,
4};
5use once_cell::sync::Lazy;
6use serde::{Deserialize, Serialize};
7use std::env;
8use std::error::Error;
9use std::time::{SystemTime, UNIX_EPOCH};
10use uuid::Uuid;
11
12#[derive(Debug, Serialize, Deserialize)]
21pub struct Claims {
22 pub sub: String,
24 pub exp: usize,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub jti: Option<Uuid>,
29}
30
31static JWT_SECRET: Lazy<Vec<u8>> = Lazy::new(|| {
32 dotenv().ok();
33 env::var("JWT_SECRET")
34 .expect("JWT_SECRET must be set")
35 .into_bytes()
36});
37
38static JWT_EXPIRY_HOURS: Lazy<u64> = Lazy::new(|| {
39 dotenv().ok();
40 env::var("JWT_EXPIRY_HOURS")
41 .unwrap_or_else(|_| "24".to_string())
42 .parse()
43 .expect("Invalid JWT_EXPIRY_HOURS")
44});
45
46pub fn get_jwt_secret() -> &'static [u8] {
47 &JWT_SECRET
48}
49
50pub fn get_jwt_expiry() -> u64 {
51 *JWT_EXPIRY_HOURS
52}
53
54pub fn create_token(aggregate_id: &str) -> Result<(String, Uuid), jsonwebtoken::errors::Error> {
58 let now_secs = SystemTime::now()
59 .duration_since(UNIX_EPOCH)
60 .unwrap()
61 .as_secs() as usize;
62 let exp = now_secs + (get_jwt_expiry() * 3600) as usize;
63 let jti = Uuid::new_v4();
64 let claims = Claims {
65 sub: aggregate_id.to_string(),
66 exp,
67 jti: Some(jti),
68 };
69 let token = encode(
70 &Header::default(),
71 &claims,
72 &EncodingKey::from_secret(get_jwt_secret()),
73 )?;
74 Ok((token, jti))
75}
76
77pub fn decode_token(token: &str) -> Result<Claims, Box<dyn Error + Send + Sync>> {
80 let validation = Validation::new(Algorithm::HS256);
81 let data: TokenData<Claims> = decode(
82 token,
83 &DecodingKey::from_secret(get_jwt_secret()),
84 &validation,
85 )?;
86 Ok(data.claims)
87}
88
89#[allow(dead_code)]
93pub fn validate_token(token: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
94 Ok(decode_token(token)?.sub)
95}