shaperail_runtime/auth/
extractor.rs1use std::future::{ready, Ready};
2use std::sync::Arc;
3
4use actix_web::dev::Payload;
5use actix_web::{web, FromRequest, HttpRequest};
6use shaperail_core::ShaperailError;
7
8use super::api_key::ApiKeyStore;
9use super::jwt::JwtConfig;
10
11#[derive(Debug, Clone)]
24pub struct AuthenticatedUser {
25 pub id: String,
27 pub role: String,
29 pub tenant_id: Option<String>,
31}
32
33impl FromRequest for AuthenticatedUser {
34 type Error = ShaperailError;
35 type Future = Ready<Result<Self, Self::Error>>;
36
37 fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
38 ready(extract_auth(req))
39 }
40}
41
42fn extract_auth(req: &HttpRequest) -> Result<AuthenticatedUser, ShaperailError> {
48 if let Some(auth_header) = req.headers().get("Authorization") {
50 let header_str = auth_header
51 .to_str()
52 .map_err(|_| ShaperailError::Unauthorized)?;
53 if let Some(token) = header_str.strip_prefix("Bearer ") {
54 let jwt_config = req
55 .app_data::<web::Data<Arc<JwtConfig>>>()
56 .ok_or(ShaperailError::Internal("JWT not configured".to_string()))?;
57 let claims = jwt_config
58 .decode(token)
59 .map_err(|_| ShaperailError::Unauthorized)?;
60 if claims.token_type != "access" {
61 return Err(ShaperailError::Unauthorized);
62 }
63 return Ok(AuthenticatedUser {
64 id: claims.sub,
65 role: claims.role,
66 tenant_id: claims.tenant_id,
67 });
68 }
69 }
70
71 if let Some(api_key_header) = req.headers().get("X-API-Key") {
73 let key = api_key_header
74 .to_str()
75 .map_err(|_| ShaperailError::Unauthorized)?;
76 let store = req
77 .app_data::<web::Data<Arc<ApiKeyStore>>>()
78 .ok_or(ShaperailError::Unauthorized)?;
79 if let Some(user) = store.lookup(key) {
80 return Ok(user);
81 }
82 return Err(ShaperailError::Unauthorized);
83 }
84
85 Err(ShaperailError::Unauthorized)
86}
87
88pub fn try_extract_auth(req: &HttpRequest) -> Option<AuthenticatedUser> {
90 extract_auth(req).ok()
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn authenticated_user_debug() {
99 let user = AuthenticatedUser {
100 id: "u1".to_string(),
101 role: "admin".to_string(),
102 tenant_id: None,
103 };
104 assert_eq!(user.id, "u1");
105 assert_eq!(user.role, "admin");
106 let cloned = user.clone();
108 assert_eq!(cloned.id, "u1");
109 }
110}