rocket_grants/
fairing.rs

1use crate::authorities::AttachAuthorities;
2use futures_core::future::BoxFuture;
3use rocket::fairing::{Fairing, Info, Kind};
4use rocket::{Data, Request};
5use std::collections::HashSet;
6use std::hash::Hash;
7
8type Extractor<Type> = Box<
9    dyn for<'a> Fn(&'a mut Request<'_>) -> BoxFuture<'a, Option<HashSet<Type>>>
10        + Send
11        + Sync
12        + 'static,
13>;
14
15/// Built-in fairing for extracting user permission.
16///
17///
18/// # Examples
19/// ```
20/// use std::collections::HashSet;
21/// use rocket::{get, Route, Response, http::Status};
22///
23/// use rocket_grants::authorities::{AuthDetails, AuthoritiesCheck};
24/// use rocket_grants::GrantsFairing;
25///
26/// #[rocket::launch]
27/// fn rocket() -> _ {
28///     rocket::build().mount("/api", rocket::routes![endpoint])
29///         .attach(GrantsFairing::with_extractor_fn(|req| {
30///             Box::pin(extract(req)) // example with a separate async function, but you can write a closure right here
31///         }))
32/// }
33///
34/// // Furthermore, you can use you own type instead of `String` (e.g. Enum).
35/// async fn extract(_req: &rocket::Request<'_>) -> Option<HashSet<String>> {
36///    // Here is a place for your code to get user permissions/roles/authorities from a request (e.g. from a token or database).
37///
38///    // Stub example
39///    Some(HashSet::from(["ROLE_ADMIN".to_string()]))
40/// }
41///
42/// // `proc-macro` crate has additional features, like ABAC security and custom types. See examples and `proc-macro` crate docs.
43/// #[rocket_grants::protect("ROLE_ADMIN")]
44/// #[rocket::get("/")]
45/// async fn endpoint() -> Status {
46///     Status::Ok
47/// }
48/// ```
49pub struct GrantsFairing<Type> {
50    extractor: Extractor<Type>,
51}
52
53impl<Type: Eq + Hash + Send + Sync + 'static> GrantsFairing<Type> {
54    /// Creating fairing using your permission extraction function.
55    ///
56    /// You can declare `async fn` with a suitable signature or you can write a boxed closure in-place (see examples below).
57    ///
58    /// # Examples
59    /// ```
60    /// use std::collections::HashSet;
61    /// use rocket_grants::GrantsFairing;
62    ///  async fn example() {
63    ///     let string_extractor = GrantsFairing::with_extractor_fn(|req| Box::pin(extract(req)));
64    ///     let enum_extractor = GrantsFairing::with_extractor_fn(|req| Box::pin(extract_enum(req)));
65    ///
66    ///     let closure_extractor = GrantsFairing::with_extractor_fn(|req| Box::pin(async move {
67    ///         Some(HashSet::from(["WRITE_ACCESS".to_string()]))
68    ///     }));
69    /// }
70    ///
71    /// async fn extract(_req: &rocket::Request<'_>) -> Option<HashSet<String>> {
72    ///     // Here is a place for your code to get user permissions/roles/authorities from a request
73    ///      // For example from a token or database
74    ///     Some(HashSet::from(["WRITE_ACCESS".to_string()]))
75    /// }
76    ///
77    /// // Or with you own type:
78    /// #[derive(Eq, PartialEq, Hash)] // required bounds
79    /// enum Permission { WRITE, READ }
80    /// async fn extract_enum(_req: &rocket::Request<'_>) -> Option<HashSet<Permission>> {
81    ///     // Here is a place for your code to get user permissions/roles/authorities from a request
82    ///      // For example from a token, database or external service
83    ///     Some(HashSet::from([Permission::WRITE]))
84    /// }
85    /// ```
86    ///
87    pub fn with_extractor_fn<F>(extractor_fn: F) -> Self
88    where
89        F: for<'a> Fn(&'a mut Request<'_>) -> BoxFuture<'a, Option<HashSet<Type>>>
90            + Send
91            + Sync
92            + 'static,
93    {
94        Self {
95            extractor: Box::new(extractor_fn),
96        }
97    }
98}
99
100#[rocket::async_trait]
101impl<Type: Eq + Hash + Send + Sync + 'static> Fairing for GrantsFairing<Type> {
102    fn info(&self) -> Info {
103        Info {
104            name: "Rocket-Grants Extractor",
105            kind: Kind::Request,
106        }
107    }
108
109    async fn on_request(&self, mut req: &mut Request<'_>, _data: &mut Data<'_>) {
110        let authorities: Option<HashSet<Type>> = (self.extractor)(req).await;
111        req.attach(authorities);
112    }
113}