fad06fd
use std::time::{SystemTime, UNIX_EPOCH};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, thiserror::Error)]
pub enum JwtError {
#[error("jwt secret not configured")]
NotConfigured,
#[error("jwt invalid: {0}")]
Invalid(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppJwtClaims {
pub sub: String,
pub token_hash: String,
pub jti: String,
pub appid: String,
pub iat: i64,
pub exp: i64,
}
impl AppJwtClaims {
pub fn uid(&self) -> Option<Uuid> {
Uuid::parse_str(&self.sub).ok()
}
pub fn jti_uuid(&self) -> Option<Uuid> {
Uuid::parse_str(&self.jti).ok()
}
}
fn now_secs() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64
}
pub fn issue_app_jwt(
secret: &str,
appid: &str,
uid: Uuid,
token_hash: &str,
ttl_secs: u64,
) -> Result<String, JwtError> {
if secret.is_empty() {
return Err(JwtError::NotConfigured);
}
let iat = now_secs();
let claims = AppJwtClaims {
sub: uid.to_string(),
token_hash: token_hash.to_string(),
jti: Uuid::now_v7().to_string(),
appid: appid.to_string(),
iat,
exp: iat + ttl_secs as i64,
};
encode(
&Header::new(Algorithm::HS256),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
.map_err(|e| JwtError::Invalid(e.to_string()))
}
pub fn verify_app_jwt(secret: &str, appid: &str, token: &str) -> Result<AppJwtClaims, JwtError> {
if secret.is_empty() {
return Err(JwtError::NotConfigured);
}
let data = decode::<AppJwtClaims>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::new(Algorithm::HS256),
)
.map_err(|e| JwtError::Invalid(e.to_string()))?;
if data.claims.appid != appid {
return Err(JwtError::Invalid("appid mismatch".to_string()));
}
Ok(data.claims)
}