kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! JWT authentication for Kegani
//!
//! Provides JWT token generation and verification.

use crate::auth::Claims;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};

/// JWT configuration
#[derive(Debug, Clone)]
pub struct JwtConfig {
    pub secret: String,
    pub issuer: String,
    pub audience: String,
    pub expiry: Duration,
}

impl JwtConfig {
    /// Create a new JWT config
    pub fn new(secret: &str) -> Self {
        Self {
            secret: secret.to_string(),
            issuer: "kegani".to_string(),
            audience: "kegani-api".to_string(),
            expiry: Duration::from_secs(3600 * 24), // 24 hours default
        }
    }

    /// Set the issuer
    pub fn issuer(mut self, issuer: &str) -> Self {
        self.issuer = issuer.to_string();
        self
    }

    /// Set the audience
    pub fn audience(mut self, audience: &str) -> Self {
        self.audience = audience.to_string();
        self
    }

    /// Set the expiry duration
    pub fn expiry(mut self, duration: Duration) -> Self {
        self.expiry = duration;
        self
    }
}

/// JWT authentication
pub struct JwtAuth {
    config: JwtConfig,
}

impl JwtAuth {
    /// Create a new JWT authenticator
    pub fn new(config: JwtConfig) -> Self {
        Self { config }
    }

    /// Generate a new token
    pub fn generate(&self, claims: &Claims) -> Result<String, JwtError> {
        use jsonwebtoken::{encode, Header, Algorithm, EncodingKey};

        let header = Header::new(Algorithm::HS256);

        let now = Instant::now().elapsed().as_secs();

        #[derive(Serialize)]
        struct JwtClaims {
            sub: String,
            iat: i64,
            exp: i64,
            roles: Vec<String>,
        }

        let jwt_claims = JwtClaims {
            sub: claims.subject.clone(),
            iat: now as i64,
            exp: (now + self.config.expiry.as_secs()) as i64,
            roles: claims.roles.clone(),
        };

        let key = EncodingKey::from_secret(self.config.secret.as_bytes());

        encode(&header, &jwt_claims, &key)
            .map_err(|e| JwtError::Generation(e.to_string()))
    }

    /// Verify and decode a token
    pub fn verify(&self, token: &str) -> Result<Claims, JwtError> {
        use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};

        let mut validation = Validation::new(Algorithm::HS256);
        validation.set_issuer(&[&self.config.issuer]);
        validation.set_audience(&[&self.config.audience]);

        let key = DecodingKey::from_secret(self.config.secret.as_bytes());

        #[derive(Deserialize)]
        struct JwtClaims {
            sub: String,
            roles: Vec<String>,
        }

        let token_data = decode::<JwtClaims>(token, &key, &validation)
            .map_err(|e| JwtError::Verification(e.to_string()))?;

        Ok(Claims {
            subject: token_data.claims.sub,
            roles: token_data.claims.roles,
            extra: None,
        })
    }
}

/// JWT error
#[derive(Debug)]
pub enum JwtError {
    Generation(String),
    Verification(String),
}

impl std::fmt::Display for JwtError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            JwtError::Generation(e) => write!(f, "JWT generation error: {}", e),
            JwtError::Verification(e) => write!(f, "JWT verification error: {}", e),
        }
    }
}

impl std::error::Error for JwtError {}