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 has_role(&self, role: &str) -> bool {
31 self.role_hierarchy.has_role(&self.claims.roles, role)
32 }
33
34 pub fn require_role(&self, role: &str) -> Result<(), AppError> {
36 if self.has_role(role) {
37 Ok(())
38 } else {
39 Err(AppError::Forbidden(format!(
40 "Role '{}' required",
41 role
42 )))
43 }
44 }
45
46 pub fn sub(&self) -> &str {
48 &self.claims.sub
49 }
50
51 pub fn user_id(&self) -> Option<i64> {
53 self.claims.user_id
54 }
55
56 pub fn user_uuid(&self) -> Option<&str> {
58 self.claims.user_uuid.as_deref()
59 }
60
61 pub fn username(&self) -> &str {
63 &self.claims.username
64 }
65}
66
67impl FromRequestParts<AppState> for AuthGuard {
68 type Rejection = AppError;
69
70 async fn from_request_parts(
71 parts: &mut Parts,
72 state: &AppState,
73 ) -> Result<Self, Self::Rejection> {
74 let auth_header = parts
76 .headers
77 .get(AUTHORIZATION)
78 .and_then(|value| value.to_str().ok())
79 .ok_or_else(|| AppError::Unauthorized("Missing Authorization header".to_string()))?;
80
81 let token = auth_header
82 .strip_prefix("Bearer ")
83 .ok_or_else(|| AppError::Unauthorized("Invalid Authorization format".to_string()))?;
84
85 let jwt = super::JwtManager::new(&state.config.jwt_secret, state.config.jwt_expiration);
87 let claims = jwt.verify(token).map_err(|_| {
88 AppError::Unauthorized("Invalid or expired token".to_string())
89 })?;
90
91 Ok(AuthGuard {
92 claims,
93 role_hierarchy: state.role_hierarchy.clone(),
94 })
95 }
96}
97
98pub async fn require_admin(
100 auth: AuthGuard,
101 request: Request,
102 next: Next,
103) -> Result<Response, AppError> {
104 auth.require_role("ROLE_ADMIN")?;
105 Ok(next.run(request).await)
106}
107
108pub async fn require_redacteur(
110 auth: AuthGuard,
111 request: Request,
112 next: Next,
113) -> Result<Response, AppError> {
114 auth.require_role("ROLE_REDACTEUR")?;
115 Ok(next.run(request).await)
116}