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}