1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::auth::{permissions::Permission, AccessTokenClaims, ID};
use actix_http::header::HeaderValue;
use actix_web::dev::Payload;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::{FromRequest, HttpRequest, HttpResponse};
use derive_more::{Display, Error};
use futures::future::{ready, Ready};
use jsonwebtoken::decode;
use jsonwebtoken::DecodingKey;
use jsonwebtoken::Validation;
use serde_json::json;
use std::collections::HashSet;
use std::iter::FromIterator;

#[derive(Debug, Clone)]
pub struct Auth {
    pub user_id: ID,
    pub roles: HashSet<String>,
    pub permissions: HashSet<Permission>,
}

impl Auth {
    pub fn has_permission(&self, permission: String) -> bool {
        self.permissions.contains(&Permission {
            permission: permission.to_string(),
            from_role: String::new(),
        })
    }

    pub fn has_all_permissions(&self, perms: Vec<String>) -> bool {
        perms.iter().all(|p| self.has_permission(p.to_string()))
    }

    pub fn has_any_permission(&self, perms: Vec<String>) -> bool {
        perms.iter().any(|p| self.has_permission(p.to_string()))
    }

    pub fn has_role(&self, permission: String) -> bool {
        self.roles.contains(&permission.to_string())
    }

    pub fn has_all_roles(&self, roles: Vec<String>) -> bool {
        roles.iter().all(|r| self.has_role(r.to_string()))
    }

    pub fn has_any_roles(&self, roles: Vec<String>) -> bool {
        roles.iter().any(|r| self.has_role(r.to_string()))
    }
}

#[derive(Debug, Display, Error)]
#[display(fmt = "Unauthorized, reason: {}", self.reason)]
pub struct AuthError {
    reason: String,
}

impl ResponseError for AuthError {
    fn error_response(&self) -> HttpResponse {
        // HttpResponse::Unauthorized().json(self.reason.as_str())
        // println!("error_response");
        HttpResponse::build(StatusCode::UNAUTHORIZED).body(
            json!({
              "message": self.reason.as_str()
            })
            .to_string(),
        )
    }

    fn status_code(&self) -> StatusCode {
        StatusCode::UNAUTHORIZED
    }
}

impl FromRequest for Auth {
    type Future = Ready<Result<Self, Self::Error>>;
    type Error = AuthError;

    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> <Self as FromRequest>::Future {
        let auth_header_opt: Option<&HeaderValue> = req.headers().get("Authorization");

        if auth_header_opt.is_none() {
            return ready(Err(AuthError {
                reason: "Authorization header required".to_string(),
            }));
        }

        let access_token_str = auth_header_opt.unwrap().to_str().unwrap_or("");

        let access_token = decode::<AccessTokenClaims>(
            access_token_str,
            &DecodingKey::from_secret(std::env::var("SECRET_KEY").unwrap().as_ref()),
            &Validation::default(),
        );

        if access_token.is_err() {
            return ready(Err(AuthError {
                reason: "Invalid access token".to_string(),
            }));
        }

        let access_token = access_token.unwrap();

        if !access_token
            .claims
            .token_type
            .eq_ignore_ascii_case("access_token")
        {
            return ready(Err(AuthError {
                reason: "Invalid access token".to_string(),
            }));
        }

        let user_id = access_token.claims.sub;
        let permissions: HashSet<Permission> =
            HashSet::from_iter(access_token.claims.permissions.iter().cloned());
        let roles: HashSet<String> = HashSet::from_iter(access_token.claims.roles.iter().cloned());

        return ready(Ok(Auth {
            user_id,
            roles,
            permissions,
        }));
    }
}