create_rust_app/auth/extractors/
auth_actixweb.rs1use super::auth::Auth;
2use crate::auth::{permissions::Permission, AccessTokenClaims};
3use actix_web::dev::Payload;
4use actix_web::error::ResponseError;
5use actix_web::http::StatusCode;
6use actix_web::{FromRequest, HttpRequest, HttpResponse};
7use derive_more::{Display, Error};
8use futures::future::{ready, Ready};
9use jsonwebtoken::decode;
10use jsonwebtoken::DecodingKey;
11use jsonwebtoken::Validation;
12use serde_json::json;
13use std::collections::HashSet;
14
15#[derive(Debug, Display, Error)]
16#[display(fmt = "Unauthorized ({status:?}), reason: {reason:?}")]
17pub struct AuthError {
19 reason: String,
20 status: StatusCode,
21}
22
23impl AuthError {
24 #[must_use]
25 pub const fn new(reason: String, status: StatusCode) -> Self {
26 Self { reason, status }
27 }
28
29 #[must_use]
30 pub const fn reason(reason: String) -> Self {
31 Self {
32 reason,
33 status: StatusCode::UNAUTHORIZED,
34 }
35 }
36}
37
38impl ResponseError for AuthError {
39 fn error_response(&self) -> HttpResponse {
41 HttpResponse::build(self.status_code()).body(
42 json!({
43 "message": self.reason.as_str()
44 })
45 .to_string(),
46 )
47 }
48
49 fn status_code(&self) -> StatusCode {
51 StatusCode::UNAUTHORIZED
52 }
53}
54
55impl FromRequest for Auth {
56 type Future = Ready<Result<Self, Self::Error>>;
57 type Error = AuthError;
58
59 fn from_request(req: &HttpRequest, _payload: &mut Payload) -> <Self as FromRequest>::Future {
61 let access_token_str = match req.headers().get("Authorization").map(|h| h.to_str()) {
62 Some(Ok(auth_header)) if auth_header.starts_with("Bearer ") => auth_header,
63 Some(_) => {
64 return ready(Err(AuthError::reason(
65 "Invalid authorization header".to_string(),
66 )))
67 }
68 None => {
69 return ready(Err(AuthError::reason(
70 "Authorization header required".to_string(),
71 )))
72 }
73 };
74
75 let access_token = match decode::<AccessTokenClaims>(
76 access_token_str.trim_start_matches("Bearer "),
77 &DecodingKey::from_secret(std::env::var("SECRET_KEY").unwrap().as_ref()),
78 &Validation::default(),
79 ) {
80 Ok(token) if token.claims.token_type.eq_ignore_ascii_case("access_token") => token,
81 _ => return ready(Err(AuthError::reason("Invalid access token".to_string()))),
82 };
83
84 let user_id = access_token.claims.sub;
85 let permissions: HashSet<Permission> =
86 access_token.claims.permissions.iter().cloned().collect();
87 let roles: HashSet<String> = access_token.claims.roles.iter().cloned().collect();
88
89 ready(Ok(Self {
90 user_id,
91 roles,
92 permissions,
93 }))
94 }
95}