use crate::http::response::{IntoResponse, Response};
use crate::http::HeaderMap;
pub trait Identity: Send + Sync {
fn sub(&self) -> &str;
fn roles(&self) -> &[String];
}
pub struct NoIdentity;
impl Identity for NoIdentity {
fn sub(&self) -> &str {
""
}
fn roles(&self) -> &[String] {
&[]
}
}
pub struct GuardContext<'a, I: Identity> {
pub method_name: &'static str,
pub controller_name: &'static str,
pub headers: &'a HeaderMap,
pub identity: Option<&'a I>,
}
impl<'a, I: Identity> GuardContext<'a, I> {
pub fn identity_sub(&self) -> Option<&str> {
self.identity.map(|i| i.sub())
}
pub fn identity_roles(&self) -> Option<&[String]> {
self.identity.map(|i| i.roles())
}
}
pub trait Guard<S, I: Identity>: Send + Sync {
fn check(&self, state: &S, ctx: &GuardContext<'_, I>) -> Result<(), Response>;
}
pub struct RolesGuard {
pub required_roles: &'static [&'static str],
}
impl<S, I: Identity> Guard<S, I> for RolesGuard {
fn check(&self, _state: &S, ctx: &GuardContext<'_, I>) -> Result<(), Response> {
let identity = ctx.identity.ok_or_else(|| {
crate::AppError::Forbidden("No identity available for role check".into())
.into_response()
})?;
let roles = identity.roles();
let has_role = self
.required_roles
.iter()
.any(|req| roles.iter().any(|r| r.as_str() == *req));
if has_role {
Ok(())
} else {
Err(crate::AppError::Forbidden("Insufficient roles".into()).into_response())
}
}
}