cargo_smith/common/
guards.rs1use actix_web::{FromRequest, HttpRequest, HttpMessage, dev::Payload};
2use futures::future::{ready, Ready};
3use serde_json::Value;
4use std::marker::PhantomData;
5use crate::auth::Claims;
6use crate::common::errors::AppError;
7use crate::common::roles::{Role, RequiredRole};
8
9pub trait GuardClaims {
10 fn claims(&self) -> &Claims<Value>;
11 fn user_id(&self) -> &str { &self.claims().sub }
12 fn username(&self) -> &str {
13 self.claims().data["username"].as_str().unwrap_or("unknown")
14 }
15 fn role_str(&self) -> Option<&str> {
16 self.claims().data["role"].as_str()
17 }
18}
19
20pub struct AuthGuard(pub Claims<Value>);
23pub struct RegisteredGuard(pub Claims<Value>);
24pub struct GuestGuard(pub Claims<Value>);
25
26pub struct RequireRole<R: Role, P: RequiredRole> {
27 pub claims: Claims<Value>,
28 _role: PhantomData<R>,
29 _perm: PhantomData<P>,
30}
31
32impl GuardClaims for AuthGuard { fn claims(&self) -> &Claims<Value> { &self.0 } }
35impl GuardClaims for RegisteredGuard { fn claims(&self) -> &Claims<Value> { &self.0 } }
36impl GuardClaims for GuestGuard { fn claims(&self) -> &Claims<Value> { &self.0 } }
37
38impl<R: Role, P: RequiredRole> GuardClaims for RequireRole<R, P> {
39 fn claims(&self) -> &Claims<Value> { &self.claims }
40}
41
42impl FromRequest for AuthGuard {
45 type Error = AppError;
46 type Future = Ready<Result<Self, Self::Error>>;
47 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
48 let result = req.extensions().get::<Claims<Value>>().cloned()
49 .map(AuthGuard)
50 .ok_or(AppError::Unauthorized("missing or invalid token"));
51 ready(result)
52 }
53}
54
55impl FromRequest for RegisteredGuard {
56 type Error = AppError;
57 type Future = Ready<Result<Self, Self::Error>>;
58 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
59 let result = match req.extensions().get::<Claims<Value>>().cloned() {
60 Some(c) if c.data["kind"] == "registered" => Ok(RegisteredGuard(c)),
61 Some(_) => Err(AppError::Forbidden("registered users only")),
62 None => Err(AppError::Unauthorized("missing token")),
63 };
64 ready(result)
65 }
66}
67
68impl FromRequest for GuestGuard {
69 type Error = AppError;
70 type Future = Ready<Result<Self, Self::Error>>;
71 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
72 let result = match req.extensions().get::<Claims<Value>>().cloned() {
73 Some(c) if c.data["kind"] == "guest" => Ok(GuestGuard(c)),
74 Some(_) => Err(AppError::Forbidden("guests only")),
75 None => Err(AppError::Unauthorized("missing token")),
76 };
77 ready(result)
78 }
79}
80
81impl<R: Role, P: RequiredRole> FromRequest for RequireRole<R, P> {
82 type Error = AppError;
83 type Future = Ready<Result<Self, Self::Error>>;
84 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
85 let result = match req.extensions().get::<Claims<Value>>().cloned() {
86 Some(c) => {
87 let role_matches = c.data
88 .get("role")
89 .and_then(|r| serde_json::from_value::<R>(r.clone()).ok())
90 .map(|r| r.as_str() == P::ROLE)
91 .unwrap_or(false);
92
93 if role_matches {
94 Ok(RequireRole { claims: c, _role: PhantomData, _perm: PhantomData })
95 } else {
96 Err(AppError::Forbidden("insufficient role"))
97 }
98 }
99 None => Err(AppError::Unauthorized("missing token")),
100 };
101 ready(result)
102 }
103}