use actix_web::{
dev::{Service, ServiceRequest, ServiceResponse, Transform},
error::ErrorUnauthorized,
Error, FromRequest, HttpMessage,
};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use futures::future::{ready, Ready};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
pub trait TokenValidator: Clone + Send + Sync + 'static {
fn validate_token<'a>(
&'a self,
token: &'a str,
) -> Pin<Box<dyn Future<Output = Result<AuthInfo, Error>> + Send + 'a>>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthInfo {
pub name: String,
pub sub: String,
pub groups: Vec<String>,
}
impl std::fmt::Display for AuthInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"user.name={} user.sub={} user.groups=[{}]",
self.name,
self.sub,
if self.groups.is_empty() {
String::from("(none)")
} else {
self.groups.join(", ")
}
)
}
}
#[derive(Clone)]
pub struct Authentication<V> {
validator: V,
}
impl<V: TokenValidator> Authentication<V> {
pub fn new(validator: V) -> Self {
Self { validator }
}
}
impl<V: TokenValidator> Authentication<Box<V>> {
pub fn new_boxed(validator: Box<V>) -> Self {
Self { validator }
}
}
impl<S, B, V> Transform<S, ServiceRequest> for Authentication<V>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
V: TokenValidator,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = AuthenticationMiddleware<S, V>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(AuthenticationMiddleware {
service: Rc::new(service),
validator: self.validator.clone(),
}))
}
}
pub struct AuthenticationMiddleware<S, V: TokenValidator> {
service: Rc<S>,
validator: V,
}
impl<S, B, V> Service<ServiceRequest> for AuthenticationMiddleware<S, V>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
S::Future: 'static,
B: 'static,
V: TokenValidator,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
let validator = self.validator.clone();
let srv = self.service.clone();
Box::pin(async move {
let req_head = req.request();
let bearer_result = BearerAuth::extract(req_head).await;
match bearer_result {
Ok(bearer) => {
let token = bearer.token();
match validator.validate_token(token).await {
Ok(auth_info) => {
req.extensions_mut().insert(auth_info);
}
Err(e) => {
return Err(e);
}
}
}
Err(_) => {
return Err(ErrorUnauthorized("Missing or invalid Bearer token"));
}
}
let res = srv.call(req).await?;
Ok(res)
})
}
}