use http::StatusCode;
use crate::{extract::Extractor, header, Request};
#[derive(Debug, Clone)]
pub struct BearerToken(pub String);
impl_deref!(BearerToken, String);
#[skyzen::error(status = StatusCode::UNAUTHORIZED)]
pub enum BearerTokenError {
#[error("Missing Authorization header")]
MissingHeader,
#[error("Invalid Authorization header encoding")]
InvalidEncoding,
#[error("Authorization header must use Bearer scheme")]
NotBearer,
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn parse_bearer(request: &Request) -> Result<&str, BearerTokenError> {
let value = request
.headers()
.get(header::AUTHORIZATION)
.ok_or(BearerTokenError::MissingHeader)?
.to_str()
.map_err(|_| BearerTokenError::InvalidEncoding)?;
let (scheme, token) = value
.split_once(char::is_whitespace)
.ok_or(BearerTokenError::NotBearer)?;
if !scheme.eq_ignore_ascii_case("bearer") {
return Err(BearerTokenError::NotBearer);
}
let token = token.trim_start();
if token.is_empty() {
return Err(BearerTokenError::NotBearer);
}
Ok(token)
}
impl Extractor for BearerToken {
type Error = BearerTokenError;
async fn extract(request: &mut Request) -> Result<Self, Self::Error> {
parse_bearer(request).map(|token| Self(token.to_owned()))
}
#[cfg(feature = "openapi")]
fn openapi() -> Option<crate::openapi::ExtractorSchema> {
Some(crate::openapi::ExtractorSchema {
location: crate::openapi::ParameterLocation::Header,
content_type: None,
schema: None,
})
}
}
#[cfg(test)]
mod tests {
use http::header::AUTHORIZATION;
use skyzen_core::Extractor;
use super::{BearerToken, BearerTokenError};
#[tokio::test]
async fn test_bearer_token_extraction() {
let mut request = http::Request::builder()
.header(AUTHORIZATION, "Bearer my-secret-token")
.body(http_kit::Body::empty())
.unwrap();
let result = BearerToken::extract(&mut request).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().0, "my-secret-token");
}
#[tokio::test]
async fn test_missing_header() {
let mut request = http::Request::builder()
.body(http_kit::Body::empty())
.unwrap();
let result = BearerToken::extract(&mut request).await;
assert!(matches!(result, Err(BearerTokenError::MissingHeader)));
}
#[tokio::test]
async fn test_not_bearer_scheme() {
let mut request = http::Request::builder()
.header(AUTHORIZATION, "Basic dXNlcjpwYXNz")
.body(http_kit::Body::empty())
.unwrap();
let result = BearerToken::extract(&mut request).await;
assert!(matches!(result, Err(BearerTokenError::NotBearer)));
}
#[tokio::test]
async fn test_bearer_without_space() {
let mut request = http::Request::builder()
.header(AUTHORIZATION, "Bearertoken")
.body(http_kit::Body::empty())
.unwrap();
let result = BearerToken::extract(&mut request).await;
assert!(matches!(result, Err(BearerTokenError::NotBearer)));
}
}