use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use http::StatusCode;
use http::header::AUTHORIZATION;
use tako_rs_core::middleware::IntoMiddleware;
use tako_rs_core::middleware::Next;
use tako_rs_core::responder::Responder;
use tako_rs_core::types::Request;
use tako_rs_core::types::Response;
use super::revocation::IntrospectionFn;
use super::revocation::RevocationCheck;
use super::revocation::RevocationList;
use super::verifier::JwtVerifier;
use super::verifier::VerifyConstraints;
pub struct JwtAuth<V: JwtVerifier> {
verifier: V,
constraints: VerifyConstraints,
revocation: Option<RevocationCheck<V::Claims>>,
introspect: Option<IntrospectionFn>,
}
impl<V: JwtVerifier> JwtAuth<V> {
pub fn new(verifier: V) -> Self {
Self {
verifier,
constraints: VerifyConstraints::default(),
revocation: None,
introspect: None,
}
}
pub fn constraints(mut self, c: VerifyConstraints) -> Self {
self.constraints = c;
self
}
pub fn revocation<R, F>(mut self, list: R, extractor: F) -> Self
where
R: RevocationList,
F: Fn(&V::Claims) -> Option<String> + Send + Sync + 'static,
{
self.revocation = Some((Arc::new(list), Arc::new(extractor)));
self
}
pub fn introspect<F, Fut>(mut self, f: F) -> Self
where
F: Fn(&str) -> Fut + Send + Sync + 'static,
Fut: Future<Output = bool> + Send + 'static,
{
self.introspect = Some(Arc::new(move |t: &str| Box::pin(f(t))));
self
}
}
impl<V: JwtVerifier> IntoMiddleware for JwtAuth<V> {
fn into_middleware(
self,
) -> impl Fn(Request, Next) -> Pin<Box<dyn Future<Output = Response> + Send + 'static>>
+ Clone
+ Send
+ Sync
+ 'static {
let verifier = self.verifier;
let constraints = Arc::new(self.constraints);
let revocation = self.revocation;
let introspect = self.introspect;
move |mut req: Request, next: Next| {
let verifier = verifier.clone();
let constraints = constraints.clone();
let revocation = revocation.clone();
let introspect = introspect.clone();
Box::pin(async move {
let token = match req
.headers()
.get(AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|s| s.split_once(' '))
.filter(|(scheme, _)| scheme.eq_ignore_ascii_case("Bearer"))
.map(|(_, rest)| rest.trim())
{
Some(t) => t.to_string(),
None => {
return (
StatusCode::UNAUTHORIZED,
"Missing or invalid Authorization header",
)
.into_response();
}
};
let claims = match verifier.verify(&token) {
Ok(c) => c,
Err(e) => {
return (StatusCode::UNAUTHORIZED, format!("Invalid token: {e}")).into_response();
}
};
if let Err(e) = verifier.validate_constraints(&claims, &constraints) {
return (StatusCode::UNAUTHORIZED, format!("Invalid token: {e}")).into_response();
}
if let Some((list, extractor)) = revocation.as_ref()
&& let Some(jti) = extractor(&claims)
&& list.is_revoked(&jti)
{
return (StatusCode::UNAUTHORIZED, "token revoked").into_response();
}
if let Some(introspect) = introspect.as_ref()
&& !introspect(&token).await
{
return (StatusCode::UNAUTHORIZED, "token introspection failed").into_response();
}
req.extensions_mut().insert(claims);
next.run(req).await.into_response()
})
}
}
}