1use async_trait::async_trait;
2
3use crate::http::AppError;
4
5use super::context::AuthContext;
6
7#[async_trait]
9pub trait Guard: Send + Sync {
10 async fn can_activate(&self, ctx: Option<&AuthContext>) -> Result<(), AppError>;
11}
12
13#[derive(Debug, Default, Clone, Copy)]
15pub struct JwtGuard;
16
17#[async_trait]
18impl Guard for JwtGuard {
19 async fn can_activate(&self, ctx: Option<&AuthContext>) -> Result<(), AppError> {
20 if ctx.is_some() {
21 Ok(())
22 } else {
23 Err(AppError::unauthorized(
24 "authentication required — provide Authorization: Bearer <token>",
25 ))
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
32pub struct RolesGuard {
33 roles: Vec<String>,
34}
35
36impl RolesGuard {
37 pub fn new(roles: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
38 Self {
39 roles: roles.into_iter().map(|r| r.as_ref().to_string()).collect(),
40 }
41 }
42
43 pub fn require(roles: &[&str]) -> Self {
44 Self::new(roles.iter().copied())
45 }
46}
47
48#[async_trait]
49impl Guard for RolesGuard {
50 async fn can_activate(&self, ctx: Option<&AuthContext>) -> Result<(), AppError> {
51 let Some(ctx) = ctx else {
52 return Err(AppError::unauthorized("authentication required"));
53 };
54 let required: Vec<&str> = self.roles.iter().map(String::as_str).collect();
55 ctx.claims.require_roles(&required)
56 }
57}
58
59pub async fn run_guards(guards: &[&dyn Guard], ctx: Option<&AuthContext>) -> Result<(), AppError> {
61 for guard in guards {
62 guard.can_activate(ctx).await?;
63 }
64 Ok(())
65}