use std::sync::Arc;
use axum::extract::{Request, State};
use axum::http::{header, StatusCode};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use backbone_auth::jwt::JwtService;
use uuid::Uuid;
use super::{AuthPartnerId, IsAdmin};
#[derive(Clone)]
pub struct UserScope {
jwt: Arc<JwtService>,
admin_partners: Arc<Vec<Uuid>>,
}
impl UserScope {
pub fn new(jwt_secret: &str, admin_partners: Vec<Uuid>) -> Self {
Self {
jwt: Arc::new(JwtService::new(jwt_secret)),
admin_partners: Arc::new(admin_partners),
}
}
fn is_admin(&self, partner: Uuid) -> bool {
self.admin_partners.contains(&partner)
}
}
pub async fn user_scope(State(scope): State<UserScope>, mut req: Request, next: Next) -> Response {
let bearer = req
.headers()
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "));
let Some(token) = bearer else {
return next.run(req).await;
};
let claims = match scope.jwt.validate_token(token) {
Ok(c) => c,
Err(e) => {
tracing::warn!(target: "auth::user_scope", error = %e, "invalid bearer token");
return invalid_token();
}
};
let partner: Uuid = match claims.sub.parse() {
Ok(id) => id,
Err(_) => {
tracing::warn!(target: "auth::user_scope", "token sub is not a partner uuid");
return invalid_token();
}
};
req.extensions_mut().insert(AuthPartnerId(partner));
req.extensions_mut().insert(IsAdmin(scope.is_admin(partner)));
next.run(req).await
}
fn invalid_token() -> Response {
(
StatusCode::UNAUTHORIZED,
"invalid or expired bearer token",
)
.into_response()
}