ncryptf/rocket/
auth.rs

1#[doc(hidden)]
2pub use base64;
3#[doc(hidden)]
4pub use chrono::{DateTime, Utc};
5#[doc(hidden)]
6pub use constant_time_eq;
7#[doc(hidden)]
8pub use rocket::{
9    async_trait,
10    http::Status,
11    data::{FromData, Outcome},
12    request::{self},
13};
14#[doc(hidden)]
15pub use rocket::figment::Figment;
16
17pub use serde::Deserialize;
18
19/// An enum of the various authentication errors that may occur - generalized
20#[derive(Debug)]
21pub enum TokenError {
22    InvalidToken,
23    SignatureInvalid,
24    InvalidRequest,
25    ServerError,
26}
27
28/// AuthorizationTrait is a trait that should be implemented by your User entity.
29/// This trait, in conjunction with the ncryptf::auth!() macro, enables individual
30/// Users to be returned as part of a Rocket request guard.
31#[async_trait::async_trait]
32pub trait AuthorizationTrait: Sync + Send + 'static {
33    /// Returns a ncryptf::Token instance given an access_token. The `databases.*` figment is provided to construct the relevant database connections as needed to process this request.
34    async fn get_token_from_access_token(
35        access_token: String,
36        figment: Figment,
37    ) -> Result<crate::Token, TokenError>;
38
39    /// Returns a <Self> (User) entity given a specific token
40    /// You can use this method to determine the appropriate access credentials, scoping, and permissions.
41    /// The `databases.*` figment is provided to construct the relevant database connections as needed to process this request.
42    async fn get_user_from_token(
43        token: crate::Token,
44        figment: Figment,
45    ) -> Result<Box<Self>, TokenError>;
46}
47
48#[derive(Debug, Clone)]
49pub struct RequestData<T: AuthorizationTrait> {
50    pub identity: T,
51    pub data: String
52}
53
54impl<T: AuthorizationTrait + Clone> RequestData<T> {
55    pub fn get_identity(&self) -> T {
56        return self.identity.clone();
57    }
58
59    pub fn get_data<D: rocket::serde::DeserializeOwned>(&self) -> Result<crate::rocket::Json<D>, anyhow::Error> {
60        match crate::rocket::Json::<D>::from_str(self.data.clone().as_str()) {
61            Ok(s) => Ok(s),
62            Err(e) => Err(anyhow::anyhow!(e.to_string()))
63        }
64    }
65}
66
67use crate::Authorization;
68
69/// The Authenticated user can be retrieved on a HTTP Request with a body by setting the <data> attribute on your request to the following
70/// ``
71///  ```
72///  #[post("/auth_info", data="<data>")]
73///  fn auth_echo(_user: ncryptf::rocket::RequestData<User>){
74///      dbg!(_user.identity); // The identity <User>
75///      dbg!(_user.data) // The raw String data, convert to type struct via ncryptf::rocket::Json::<T>::from_str()
76///  }
77///  ```
78#[crate::rocket::async_trait]
79impl<'r, T: AuthorizationTrait> FromData<'r> for RequestData<T> {
80    type Error = TokenError;
81
82    async fn from_data(
83        req: &'r rocket::request::Request<'_>,
84        data: rocket::Data<'r>
85    ) -> rocket::data::Outcome<'r, Self> {
86        let _ = crate::rocket::parse_body::<serde_json::Value>(req, data).await;
87        let dbs = req.rocket().figment().focus("databases");
88
89        let body = req.local_cache(|| return "".to_string());
90
91        // Retrieve the Authorization header
92        let header: String = match req.headers().get_one("Authorization") {
93            Some(h) => h.to_string(),
94            None => return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
95        };
96
97        let params = match crate::Authorization::extract_params_from_header_string(header) {
98            Ok(params) => params,
99            Err(_) => return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
100        };
101
102        match <T>::get_token_from_access_token(params.access_token, dbs.clone()).await {
103            Ok(token) => {
104                // Create a new datetime from the data parameter, or the request header
105                let date: crate::rocket::DateTime<Utc> = match params.date {
106                    Some(date) => date,
107                    None => {
108                        let date: crate::rocket::DateTime<Utc> = match req.headers().get_one("X-Date") {
109                            Some(h) => {
110                                let date = crate::rocket::DateTime::parse_from_rfc2822(&h.to_string());
111                                date.unwrap().with_timezone(&Utc)
112                            },
113                            None => {
114                                return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken));
115                            }
116                        };
117                        date
118                    }
119                };
120
121                let method = req.method().to_string();
122                let uri = req.uri().to_string();
123                let data = body.clone();
124                match Authorization::from(
125                    method,
126                    uri,
127                    token.clone(),
128                    date,
129                    data.clone().to_owned(),
130                    Some(params.salt),
131                    params.version
132                ) {
133                    Ok(auth) => {
134                        if auth.verify(params.hmac, crate::rocket::NCRYPTF_DRIFT_ALLOWANCE) {
135                            match <T>::get_user_from_token(token, dbs).await {
136                                Ok(user) => return Outcome::Success(RequestData { identity: *user, data: data.clone() }),
137                                Err(_) => return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
138                            };
139                        }
140                    },
141                    Err(_) => return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
142                };
143            },
144            Err(_) => return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
145        };
146
147        return Outcome::Error((Status::Unauthorized, TokenError::InvalidToken))
148    }
149}
150
151/// The ncryptf::auth!() macro provides the appropriate generic implementation details of FromRequest to allow User entities to be returned
152/// as a Rocket request guard (FromRequest). The core features of ncryptf authorization verification are implemented through this macro.
153/// If you wish to utilize ncryptf's authorization features you must perform the following.
154///
155/// ### Usage
156/// 1. Attach the ncryptf::Fairing to your Rocket configuration:
157///  ```rust
158///  let rocket = rocket::custom(config)
159///      .attach(NcryptfFairing);
160///  ```
161///  2. Define your User entity, and have to implement AuthorizationTrait.
162///  3. At the end of your User entity struct file, bind the macro FromRequest to your User entity.
163///  ```rust
164///  ncryptf::auth!(User);
165///  ```
166///  4. Your User is now available as part of the request guard:
167///  ```
168///  #[get("/auth_info")]
169///  fn auth_echo(_user: User){
170///      dbg!(_user);
171///  }
172///  ```
173///  **NOTE**: The Authorization Features of ncryptf are exclusively available if and only if you set the appropriate Content-Type to either application/json, or application/vnd.ncryptf+json, _even for GET requests_,
174///  and other requests that don't have a body. The FromRequest functionality is only available for these content types.
175///  Additionally, ncryptf::rocket::Json will handle all JSON + Ncryptf+JSON content types when this is in use. ncryptf::rocket::Json is mostly compatible with rocket::serde::Json, but shares the same limitations, features,
176///  and particularities.
177#[macro_export]
178macro_rules! auth {
179    ($T: ty) => {
180        use $crate::rocket::TokenError;
181        use $crate::rocket::AuthorizationTrait;
182        use $crate::Authorization;
183        use rocket::request::{self, FromRequest, Outcome};
184
185        #[$crate::rocket::async_trait]
186        impl<'r> $crate::rocket::request::FromRequest<'r> for $T {
187            type Error = TokenError;
188
189            async fn from_request(req: &'r $crate::rocket::request::Request<'_>) -> rocket::request::Outcome<Self, TokenError> {
190                let dbs = req.rocket().figment().focus("databases");
191
192                let body = req.local_cache(|| return "".to_string());
193
194                // Retrieve the Authorization header
195                let header: String = match req.headers().get_one("Authorization") {
196                    Some(h) => h.to_string(),
197                    None => return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
198                };
199
200                let params = match $crate::Authorization::extract_params_from_header_string(header) {
201                    Ok(params) => params,
202                    Err(_) => return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
203                };
204
205                match <$T>::get_token_from_access_token(params.access_token, dbs.clone()).await {
206                    Ok(token) => {
207                        // Create a new datetime from the data parameter, or the request header
208                        let date: $crate::rocket::DateTime<$crate::rocket::Utc> = match params.date {
209                            Some(date) => date,
210                            None => {
211                                let date: $crate::rocket::DateTime<$crate::rocket::Utc> = match req.headers().get_one("X-Date") {
212                                    Some(h) => {
213                                        let date = $crate::rocket::DateTime::parse_from_rfc2822(&h.to_string());
214                                        date.unwrap().with_timezone(&$crate::rocket::Utc)
215                                    },
216                                    None => {
217                                        return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken));
218                                    }
219                                };
220                                date
221                            }
222                        };
223
224                        let method = req.method().to_string();
225                        let uri = req.uri().to_string();
226                        let data = body.to_owned();
227                        match $crate::Authorization::from(
228                            method,
229                            uri,
230                            token.clone(),
231                            date,
232                            data,
233                            Some(params.salt),
234                            params.version
235                        ) {
236                            Ok(auth) => {
237                                if auth.verify(params.hmac, $crate::rocket::NCRYPTF_DRIFT_ALLOWANCE) {
238                                    match <$T>::get_user_from_token(token, dbs).await {
239                                        Ok(user) => return rocket::request::Outcome::Success(*user),
240                                        Err(_) => return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
241                                    };
242                                }
243                            },
244                            Err(_) => return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
245                        };
246                    },
247                    Err(_) => return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
248                };
249
250                return rocket::request::Outcome::Error(($crate::rocket::Status::Unauthorized, TokenError::InvalidToken))
251            }
252        }
253    }
254}