use crate::shared::auth_token::AuthToken;
#[derive(Debug, Clone)]
pub struct AuthContext {
pub token: Option<AuthToken>,
pub path: String,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub sub: Option<String>,
pub roles: Vec<String>,
}
impl AuthContext {
#[must_use]
pub fn new(path: String, ip_address: Option<String>, user_agent: Option<String>) -> Self {
Self {
token: None,
path,
ip_address,
user_agent,
sub: None,
roles: Vec::new(),
}
}
#[must_use]
pub fn with_token(mut self, token: AuthToken) -> Self {
self.token = Some(token);
self
}
#[must_use]
pub fn is_authenticated(&self) -> bool {
self.token.as_ref().is_some_and(|t| !t.is_expired())
}
}
#[derive(Debug)]
pub enum GuardResult {
Authorized(Box<AuthContext>),
Unauthorized,
TokenExpired,
}
#[must_use]
pub fn require_auth(ctx: AuthContext) -> GuardResult {
match ctx.token {
Some(ref token) => {
if token.is_expired() {
GuardResult::TokenExpired
} else {
GuardResult::Authorized(Box::new(ctx))
}
}
None => GuardResult::Unauthorized,
}
}
#[must_use]
pub fn optional_auth(mut ctx: AuthContext) -> GuardResult {
if let Some(ref token) = ctx.token {
if token.is_expired() {
ctx.token = None;
}
}
GuardResult::Authorized(Box::new(ctx))
}
#[must_use]
pub fn extract_session_token(cookies: &[&str], cookie_name: &str) -> Option<String> {
for cookie in cookies {
if let Some(start) = cookie.find(&format!("{cookie_name}=")) {
let rest = &cookie[start + cookie_name.len() + 1..];
if let Some(end) = rest.find(';') {
return Some(rest[..end].to_string());
}
return Some(rest.to_string());
}
}
None
}
#[must_use]
pub fn extract_bearer_token(auth_header: Option<&str>) -> Option<String> {
let header = auth_header?;
const PREFIX: &[u8] = b"Bearer ";
let bytes = header.as_bytes();
if bytes.len() >= PREFIX.len() && bytes[..PREFIX.len()].eq_ignore_ascii_case(PREFIX) {
header.get(PREFIX.len()..).map(String::from)
} else {
None
}
}
#[must_use]
pub fn has_scope(ctx: &AuthContext, required: &[&str]) -> bool {
if required.is_empty() {
return true;
}
let Some(token) = &ctx.token else {
return false;
};
let token_scope = match token {
AuthToken::OAuth { scope, .. } => scope.as_deref(),
_ => None,
};
let Some(scope_str) = token_scope else {
return false;
};
let scopes: Vec<&str> = scope_str.split_whitespace().collect();
required.iter().any(|r| scopes.contains(r))
}