Skip to main content

authx_core/crypto/
signing.rs

1use chrono::Utc;
2use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
3use serde::{Deserialize, Serialize};
4use tracing::instrument;
5use uuid::Uuid;
6
7use crate::error::{AuthError, Result};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Claims {
11    pub sub: String,
12    pub exp: i64,
13    pub iat: i64,
14    pub jti: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub org: Option<String>,
17    #[serde(flatten)]
18    pub extra: serde_json::Value,
19}
20
21pub struct TokenSigner {
22    encoding: EncodingKey,
23    decoding: DecodingKey,
24}
25
26impl TokenSigner {
27    pub fn from_ed25519_pem(private_pem: &[u8], public_pem: &[u8]) -> Result<Self> {
28        let encoding = EncodingKey::from_ed_pem(private_pem)
29            .map_err(|e| AuthError::Internal(format!("invalid private key: {e}")))?;
30        let decoding = DecodingKey::from_ed_pem(public_pem)
31            .map_err(|e| AuthError::Internal(format!("invalid public key: {e}")))?;
32        Ok(Self { encoding, decoding })
33    }
34
35    #[instrument(skip(self, extra))]
36    pub fn sign(
37        &self,
38        subject: Uuid,
39        ttl_seconds: i64,
40        extra: serde_json::Value,
41    ) -> Result<String> {
42        let now = Utc::now().timestamp();
43        let claims = Claims {
44            sub: subject.to_string(),
45            exp: now + ttl_seconds,
46            iat: now,
47            jti: Uuid::new_v4().to_string(),
48            org: None,
49            extra,
50        };
51        let token = encode(&Header::new(Algorithm::EdDSA), &claims, &self.encoding)
52            .map_err(|e| AuthError::Internal(format!("jwt sign failed: {e}")))?;
53
54        tracing::debug!(sub = %subject, "jwt signed");
55        Ok(token)
56    }
57
58    #[instrument(skip(self, token))]
59    pub fn verify(&self, token: &str) -> Result<Claims> {
60        let mut validation = Validation::new(Algorithm::EdDSA);
61        validation.validate_exp = true;
62
63        let data = decode::<Claims>(token, &self.decoding, &validation)
64            .map_err(|_| AuthError::InvalidToken)?;
65
66        tracing::debug!(sub = %data.claims.sub, "jwt verified");
67        Ok(data.claims)
68    }
69}