use crate::{AuthUser, auth_user, token::AuthToken};
use async_trait::async_trait;
use umbral::web::{HeaderMap, header};
use umbral::auth::{Authentication, Identity};
#[derive(Debug, Default, Clone, Copy)]
pub struct BearerAuthentication;
impl BearerAuthentication {
pub fn new() -> Self {
Self
}
}
pub fn parse_bearer_header(headers: &HeaderMap) -> Option<&str> {
let raw = headers.get(header::AUTHORIZATION)?.to_str().ok()?;
let rest = raw
.strip_prefix("Bearer ")
.or_else(|| raw.strip_prefix("bearer "))?;
let trimmed = rest.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
}
#[async_trait]
impl Authentication for BearerAuthentication {
async fn authenticate(&self, headers: &HeaderMap) -> Option<Identity> {
let plaintext = parse_bearer_header(headers)?;
let token = AuthToken::lookup(plaintext).await.ok().flatten()?;
let user: AuthUser = AuthUser::objects()
.filter(auth_user::ID.eq(token.user_id.id()) & auth_user::IS_ACTIVE.eq(true))
.first()
.await
.ok()
.flatten()?;
token.touch_last_used().await;
Some(
Identity::user(crate::UserModel::id_string(&user))
.with_staff(user.is_staff)
.with_superuser(user.is_superuser)
.with_extra("auth", serde_json::json!("bearer")),
)
}
fn security_scheme(&self) -> Option<(String, serde_json::Value)> {
Some((
"BearerAuth".to_string(),
serde_json::json!({
"type": "http",
"scheme": "bearer",
"bearerFormat": "umbral",
"description": "umbral bearer token. Header: `Authorization: Bearer umbral_<token>`."
}),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use umbral::web::HeaderMap;
fn h(value: &str) -> HeaderMap {
let mut m = HeaderMap::new();
m.insert(header::AUTHORIZATION, value.parse().unwrap());
m
}
#[test]
fn parses_canonical_bearer() {
assert_eq!(
parse_bearer_header(&h("Bearer umbral_abc")),
Some("umbral_abc")
);
}
#[test]
fn parses_lowercase_scheme() {
assert_eq!(
parse_bearer_header(&h("bearer umbral_abc")),
Some("umbral_abc")
);
}
#[test]
fn rejects_basic_scheme() {
assert_eq!(parse_bearer_header(&h("Basic dXNlcjpwYXNz")), None);
}
#[test]
fn rejects_missing_header() {
let m = HeaderMap::new();
assert_eq!(parse_bearer_header(&m), None);
}
#[test]
fn rejects_empty_token() {
assert_eq!(parse_bearer_header(&h("Bearer ")), None);
assert_eq!(parse_bearer_header(&h("Bearer ")), None);
}
}