use crate::{AppContext, Outcome, Request, Response, middlewares::Middleware, next};
pub use jsonwebtoken::errors::Error;
pub use jsonwebtoken::errors::ErrorKind;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, encode};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
pub trait Claim: DeserializeOwned {
fn validate(&self) -> Result<(), Error> {
Ok(())
}
}
#[derive(Serialize, Deserialize)]
pub struct SimpleClaims {
pub sub: String,
pub exp: usize,
}
impl Claim for SimpleClaims {
fn validate(&self) -> Result<(), Error> {
if self.sub.is_empty() {
return Err(Error::from(jsonwebtoken::errors::ErrorKind::InvalidToken));
}
let now = ::std::time::SystemTime::now().duration_since(::std::time::UNIX_EPOCH).unwrap().as_secs() as usize;
if self.exp < now {
return Err(Error::from(jsonwebtoken::errors::ErrorKind::ExpiredSignature));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct JwtManager {
secret: String,
}
impl JwtManager {
pub fn new(secret: String) -> Self {
Self {
secret,
}
}
pub fn decode<T: for<'de> Deserialize<'de> + Claim>(&self, token: &str) -> Result<T, jsonwebtoken::errors::Error> {
let data = jsonwebtoken::decode::<T>(token, &DecodingKey::from_secret(self.secret.as_bytes()), &Validation::default())?;
data.claims.validate()?;
Ok(data.claims)
}
pub fn encode<T: Serialize>(&self, claims: &T) -> Result<String, jsonwebtoken::errors::Error> {
encode(&Header::default(), claims, &EncodingKey::from_secret(self.secret.as_bytes()))
}
pub fn generate_simple(&self, subject: &str, ttl_hours: i64) -> Result<String, jsonwebtoken::errors::Error> {
let claims = SimpleClaims {
sub: subject.to_owned(),
exp: chrono::Utc::now().checked_add_signed(chrono::Duration::hours(ttl_hours)).unwrap().timestamp() as usize,
};
self.encode(&claims)
}
}
pub fn with_jwt_auth<T, F: Send + Sync>(handler: F) -> impl Middleware
where
T: for<'de> serde::de::Deserialize<'de> + Claim + 'static,
F: Fn(&mut Request, &mut Response, &AppContext, T) -> Outcome,
{
move |req: &mut Request, res: &mut Response, ctx: &AppContext| -> Outcome {
let manager = ctx.jwt();
let token = match req.headers.get("Authorization").and_then(|h| h.to_str().ok()).and_then(|h| h.strip_prefix("Bearer ")) {
Some(t) => t,
None => {
res.set_status(401);
res.send_text("Missing or invalid Authorization header");
return next!();
}
};
let claims: T = match manager.decode(token) {
Ok(c) => c,
Err(_) => {
res.set_status(401);
res.send_text("Invalid or expired token");
return next!();
}
};
handler(req, res, ctx, claims)
}
}