karbon_framework/security/
guard.rs1use axum::{
2 extract::{FromRequestParts, Request},
3 http::{header::AUTHORIZATION, request::Parts},
4 middleware::Next,
5 response::Response,
6};
7
8use crate::error::AppError;
9use crate::http::AppState;
10
11use super::{Claims, RoleHierarchy};
12
13#[derive(Debug, Clone)]
22pub struct AuthGuard {
23 pub claims: Claims,
24 role_hierarchy: RoleHierarchy,
25}
26
27impl AuthGuard {
28 pub fn from_claims(claims: Claims) -> Self {
30 Self {
31 claims,
32 role_hierarchy: crate::security::default_hierarchy(),
33 }
34 }
35
36 pub fn has_role(&self, role: &str) -> bool {
39 self.role_hierarchy.has_role(&self.claims.roles, role)
40 }
41
42 pub fn require_role(&self, role: &str) -> Result<(), AppError> {
44 if self.has_role(role) {
45 Ok(())
46 } else {
47 Err(AppError::Forbidden(format!(
48 "Role '{}' required",
49 role
50 )))
51 }
52 }
53
54 pub fn sub(&self) -> &str {
56 &self.claims.sub
57 }
58
59 pub fn user_id(&self) -> Option<i64> {
61 self.claims.user_id
62 }
63
64 pub fn user_uuid(&self) -> Option<&str> {
66 self.claims.user_uuid.as_deref()
67 }
68
69 pub fn username(&self) -> &str {
71 &self.claims.username
72 }
73}
74
75impl FromRequestParts<AppState> for AuthGuard {
76 type Rejection = AppError;
77
78 async fn from_request_parts(
79 parts: &mut Parts,
80 state: &AppState,
81 ) -> Result<Self, Self::Rejection> {
82 let auth_header = parts
84 .headers
85 .get(AUTHORIZATION)
86 .and_then(|value| value.to_str().ok())
87 .ok_or_else(|| AppError::Unauthorized("Missing Authorization header".to_string()))?;
88
89 let token = auth_header
90 .strip_prefix("Bearer ")
91 .ok_or_else(|| AppError::Unauthorized("Invalid Authorization format".to_string()))?;
92
93 let jwt = super::JwtManager::new(&state.config.jwt_secret, state.config.jwt_expiration);
95 let claims = jwt.verify(token).map_err(|_| {
96 AppError::Unauthorized("Invalid or expired token".to_string())
97 })?;
98
99 Ok(AuthGuard {
100 claims,
101 role_hierarchy: state.role_hierarchy.clone(),
102 })
103 }
104}
105
106pub async fn require_admin(
108 auth: AuthGuard,
109 request: Request,
110 next: Next,
111) -> Result<Response, AppError> {
112 auth.require_role("ROLE_ADMIN")?;
113 Ok(next.run(request).await)
114}
115
116pub async fn require_redacteur(
118 auth: AuthGuard,
119 request: Request,
120 next: Next,
121) -> Result<Response, AppError> {
122 auth.require_role("ROLE_REDACTEUR")?;
123 Ok(next.run(request).await)
124}