use std::future::Future;
use rusty_gasket::BoxFuture;
use rusty_gasket::auth::error::AuthError;
use rusty_gasket::auth::identity::Identity;
#[must_use]
pub fn extract_bearer_token(header_value: &str) -> Option<&str> {
const PREFIX: &[u8] = b"Bearer ";
let bytes = header_value.as_bytes();
let prefix_bytes = bytes.get(..PREFIX.len())?;
if prefix_bytes.eq_ignore_ascii_case(PREFIX) {
Some(&header_value[PREFIX.len()..])
} else {
None
}
}
pub trait AuthBackend: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn authenticate<'ctx>(
&'ctx self,
headers: &'ctx http::HeaderMap,
uri: &'ctx http::Uri,
) -> impl Future<Output = Result<Option<Identity>, AuthError>> + Send + 'ctx;
}
trait ErasedAuthBackend: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn authenticate<'ctx>(
&'ctx self,
headers: &'ctx http::HeaderMap,
uri: &'ctx http::Uri,
) -> BoxFuture<'ctx, Result<Option<Identity>, AuthError>>;
}
impl<T> ErasedAuthBackend for T
where
T: AuthBackend,
{
fn name(&self) -> &'static str {
AuthBackend::name(self)
}
fn authenticate<'ctx>(
&'ctx self,
headers: &'ctx http::HeaderMap,
uri: &'ctx http::Uri,
) -> BoxFuture<'ctx, Result<Option<Identity>, AuthError>> {
Box::pin(AuthBackend::authenticate(self, headers, uri))
}
}
pub struct AuthBackendHandle {
inner: Box<dyn ErasedAuthBackend>,
}
impl AuthBackendHandle {
pub fn new(backend: impl AuthBackend) -> Self {
Self {
inner: Box::new(backend),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
self.inner.name()
}
pub(crate) fn authenticate<'ctx>(
&'ctx self,
headers: &'ctx http::HeaderMap,
uri: &'ctx http::Uri,
) -> BoxFuture<'ctx, Result<Option<Identity>, AuthError>> {
self.inner.authenticate(headers, uri)
}
}
impl std::fmt::Debug for AuthBackendHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("AuthBackendHandle")
.field(&self.name())
.finish()
}
}
pub type BoxAuthBackend = AuthBackendHandle;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_bearer_token() {
assert_eq!(extract_bearer_token("Bearer abc123"), Some("abc123"));
}
#[test]
fn case_insensitive_prefix() {
assert_eq!(extract_bearer_token("bearer xyz"), Some("xyz"));
assert_eq!(extract_bearer_token("BEARER xyz"), Some("xyz"));
assert_eq!(extract_bearer_token("BeArEr xyz"), Some("xyz"));
}
#[test]
fn rejects_non_bearer_scheme() {
assert_eq!(extract_bearer_token("Basic abc123"), None);
assert_eq!(extract_bearer_token("Token abc123"), None);
}
#[test]
fn rejects_short_input() {
assert_eq!(extract_bearer_token(""), None);
assert_eq!(extract_bearer_token("Bear"), None);
assert_eq!(extract_bearer_token("Bearer"), None);
}
#[test]
fn handles_multibyte_after_prefix() {
assert_eq!(extract_bearer_token("Bearer 日本語"), Some("日本語"));
}
#[test]
fn handles_multibyte_in_prefix_position() {
assert_eq!(extract_bearer_token("Béarer x"), None);
}
#[test]
fn empty_token_after_prefix() {
assert_eq!(extract_bearer_token("Bearer "), Some(""));
}
}