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 user_id(&self) -> &str {
48 &self.claims.sub
49 }
50
51 pub fn username(&self) -> &str {
53 &self.claims.username
54 }
55}
56
57impl FromRequestParts<AppState> for AuthGuard {
58 type Rejection = AppError;
59
60 async fn from_request_parts(
61 parts: &mut Parts,
62 state: &AppState,
63 ) -> Result<Self, Self::Rejection> {
64 let auth_header = parts
66 .headers
67 .get(AUTHORIZATION)
68 .and_then(|value| value.to_str().ok())
69 .ok_or_else(|| AppError::Unauthorized("Missing Authorization header".to_string()))?;
70
71 let token = auth_header
72 .strip_prefix("Bearer ")
73 .ok_or_else(|| AppError::Unauthorized("Invalid Authorization format".to_string()))?;
74
75 let jwt = super::JwtManager::new(&state.config.jwt_secret, state.config.jwt_expiration);
77 let claims = jwt.verify(token).map_err(|_| {
78 AppError::Unauthorized("Invalid or expired token".to_string())
79 })?;
80
81 Ok(AuthGuard {
82 claims,
83 role_hierarchy: state.role_hierarchy.clone(),
84 })
85 }
86}
87
88pub async fn require_admin(
90 auth: AuthGuard,
91 request: Request,
92 next: Next,
93) -> Result<Response, AppError> {
94 auth.require_role("ROLE_ADMIN")?;
95 Ok(next.run(request).await)
96}
97
98pub async fn require_redacteur(
100 auth: AuthGuard,
101 request: Request,
102 next: Next,
103) -> Result<Response, AppError> {
104 auth.require_role("ROLE_REDACTEUR")?;
105 Ok(next.run(request).await)
106}