rocket_auth/user/
auth.rs

1use crate::prelude::*;
2use rocket::http::Status;
3use rocket::http::{Cookie, CookieJar};
4use rocket::request::FromRequest;
5use rocket::request::Outcome;
6use rocket::Request;
7use rocket::State;
8use serde_json::json;
9use std::time::Duration;
10
11/// The [`Auth`] guard allows to log in, log out, sign up, modify, and delete the currently (un)authenticated user.
12/// For more information see [`Auth`].
13///  A working example:
14/// ```rust,no_run
15///
16/// use rocket::{*, form::Form};
17/// use rocket_auth::{Users, Error, Auth, Signup, Login};
18///
19/// #[post("/signup", data="<form>")]
20/// async fn signup(form: Form<Signup>, auth: Auth<'_>) {
21///     auth.signup(&form).await;
22///     auth.login(&form.into());
23/// }
24///
25/// #[post("/login", data="<form>")]
26/// fn login(form: Form<Login>, auth: Auth) {
27///     auth.login(&form);
28/// }
29///
30/// #[get("/logout")]
31/// fn logout(auth: Auth) {
32///     auth.logout();
33/// }
34/// #[tokio::main]
35/// async fn main() -> Result<(), Error>{
36///     let users = Users::open_sqlite("mydb.db").await?;
37///
38///     rocket::build()
39///         .mount("/", routes![signup, login, logout])
40///         .manage(users)
41///         .launch()
42///         .await;
43///     Ok(())
44/// }
45/// ```
46#[allow(missing_docs)]
47pub struct Auth<'a> {
48    /// `Auth` includes in its fields a [`Users`] instance. Therefore, it is not necessary to retrieve `Users` when using this guard.
49    pub users: &'a State<Users>,
50    pub cookies: &'a CookieJar<'a>,
51    pub session: Option<Session>,
52}
53
54#[async_trait]
55impl<'r> FromRequest<'r> for Auth<'r> {
56    type Error = Error;
57    async fn from_request(req: &'r Request<'_>) -> Outcome<Auth<'r>, Error> {
58        let session: Option<Session> = if let Outcome::Success(users) = req.guard().await {
59            Some(users)
60        } else {
61            None
62        };
63
64        let users: &State<Users> = if let Outcome::Success(users) = req.guard().await {
65            users
66        } else {
67            return Outcome::Failure((Status::InternalServerError, Error::UnmanagedStateError));
68        };
69
70        Outcome::Success(Auth {
71            users,
72            session,
73            cookies: req.cookies(),
74        })
75    }
76}
77
78impl<'a> Auth<'a> {
79    /// Logs in the user through a parsed form or json.
80    /// The session is set to expire in one year by default.
81    /// For a custom expiration date use [`Auth::login_for`].
82    /// ```rust
83    /// # use rocket::{get, post, form::Form};
84    /// # use rocket_auth::{Auth, Login};
85    /// #[post("/login", data="<form>")]
86    /// fn login(form: Form<Login>, auth: Auth) {
87    ///     auth.login(&form);
88    /// }
89    /// ```
90    #[throws(Error)]
91    pub async fn login(&self, form: &Login) {
92        let key = self.users.login(form).await?;
93        let user = self.users.get_by_email(&form.email).await?;
94        let session = Session {
95            id: user.id,
96            email: user.email,
97            auth_key: key,
98            time_stamp: now(),
99        };
100        let to_str = format!("{}", json!(session));
101        self.cookies.add_private(Cookie::new("rocket_auth", to_str));
102    }
103
104    /// Logs a user in for the specified period of time.
105    /// ```rust
106    /// # use rocket::{post, form::Form};
107    /// # use rocket_auth::{Login, Auth};
108    /// # use std::time::Duration;
109    /// #[post("/login", data="<form>")]
110    /// fn login(form: Form<Login>, auth: Auth) {
111    ///     let one_hour = Duration::from_secs(60 * 60);
112    ///     auth.login_for(&form, one_hour);
113    /// }
114    /// ```
115    #[throws(Error)]
116    pub async fn login_for(&self, form: &Login, time: Duration) {
117        let key = self.users.login_for(form, time).await?;
118        let user = self.users.get_by_email(&form.email).await?;
119
120        let session = Session {
121            id: user.id,
122            email: user.email,
123            auth_key: key,
124            time_stamp: now(),
125        };
126        let to_str = format!("{}", json!(session));
127        let cookie = Cookie::new("rocket_auth", to_str);
128        self.cookies.add_private(cookie);
129    }
130
131    /// Creates a new user from a form or a json. The user will not be authenticated by default.
132    /// In order to authenticate the user, cast the signup form to a login form or use `signup_for`.
133    /// ```rust
134    /// # use rocket::{post, form::Form};
135    /// # use rocket_auth::{Auth, Signup, Error};
136    /// # use std::time::Duration;
137    /// #[post("/signup", data="<form>")]
138    /// async fn signup(form: Form<Signup>, auth: Auth<'_>) -> Result<&'static str, Error>{
139    ///     auth.signup(&form).await?;
140    ///     auth.login(&form.into()).await?;
141    ///     Ok("Logged in")
142    /// }
143    /// ```
144    #[throws(Error)]
145    pub async fn signup(&self, form: &Signup) {
146        self.users.signup(form).await?;
147    }
148
149    /// Creates a new user from a form or a json.
150    /// The session will last the specified period of time.
151    /// ```rust
152    /// # use rocket::{post, form::Form};
153    /// # use rocket_auth::{Auth, Signup};
154    /// # use std::time::Duration;
155    /// #[post("/signup", data="<form>")]
156    /// fn signup_for(form: Form<Signup>, auth: Auth) {
157    ///     let one_hour = Duration::from_secs(60 * 60);
158    ///     auth.signup_for(&form, one_hour);
159    /// }
160    /// ```
161    #[throws(Error)]
162    pub async fn signup_for(&self, form: &Signup, time: Duration) {
163        self.users.signup(form).await?;
164        self.login_for(&form.clone().into(), time).await?;
165    }
166
167    ///
168    ///
169    /// It allows to know if the current client is authenticated or not.
170    /// ```rust
171    /// # use rocket::{get};
172    /// # use rocket_auth::{Auth};
173    /// #[get("/am-I-authenticated")]
174    /// fn is_auth(auth: Auth<'_>) -> &'static str {
175    ///     if auth.is_auth() {
176    ///         "Yes you are."
177    ///     } else {
178    ///         "nope."
179    ///     }
180    /// }
181    /// # fn main() {}
182    /// ```
183    pub fn is_auth(&self) -> bool {
184        if let Some(session) = &self.session {
185            self.users.is_auth(session)
186        } else {
187            false
188        }
189    }
190
191    /// It retrieves the current logged user.  
192    /// ```
193    /// # use rocket::get;
194    /// # use rocket_auth::Auth;
195    /// #[get("/display-me")]
196    /// async fn display_me(auth: Auth<'_>) -> String {
197    ///     format!("{:?}", auth.get_user().await)
198    /// }
199    /// ```
200    pub async fn get_user(&self) -> Option<User> {
201        if !self.is_auth() {
202            return None;
203        }
204        let id = self.session.as_ref()?.id;
205        if let Ok(user) = self.users.get_by_id(id).await {
206            Some(user)
207        } else {
208            None
209        }
210    }
211
212    /// Logs the currently authenticated user out.
213    /// ```rust
214    /// # use rocket::get;
215    /// # use rocket_auth::Auth;
216    /// #[get("/logout")]
217    /// fn logout(auth: Auth)  {
218    ///     auth.logout();
219    /// }
220    /// ```
221    #[throws(Error)]
222    pub fn logout(&self) {
223        let session = self.get_session()?;
224        self.users.logout(session)?;
225        self.cookies.remove_private(Cookie::named("rocket_auth"));
226    }
227    /// Deletes the account of the currently authenticated user.
228    /// ```rust
229    /// # use rocket::get;
230    /// # use rocket_auth::Auth;
231    /// #[get("/delete-my-account")]
232    /// fn delete(auth: Auth)  {
233    ///     auth.delete();
234    /// }
235    /// ```
236    #[throws(Error)]
237    pub async fn delete(&self) {
238        if self.is_auth() {
239            let session = self.get_session()?;
240            self.users.delete(session.id).await?;
241            self.cookies.remove_private(Cookie::named("rocket_auth"));
242        } else {
243            throw!(Error::UnauthenticatedError)
244        }
245    }
246
247    /// Changes the password of the currently authenticated user
248    /// ```
249    /// # use rocket_auth::Auth;
250    /// # use rocket::post;
251    /// # #[post("/change")]
252    /// # fn example(auth: Auth<'_>) {
253    ///     auth.change_password("new password");
254    /// # }
255    /// ```
256    #[throws(Error)]
257    pub async fn change_password(&self, password: &str) {
258        if self.is_auth() {
259            let session = self.get_session()?;
260            let mut user = self.users.get_by_id(session.id).await?;
261            user.set_password(password)?;
262            self.users.modify(&user).await?;
263        } else {
264            throw!(Error::UnauthorizedError)
265        }
266    }
267
268    /// Changes the email of the currently authenticated user
269    /// ```
270    /// # use rocket_auth::Auth;
271    /// # fn func(auth: Auth) {
272    /// auth.change_email("new@email.com".into());
273    /// # }
274    /// ```
275    #[throws(Error)]
276    pub async fn change_email(&self, email: String) {
277        if self.is_auth() {
278            if !validator::validate_email(&email) {
279                throw!(Error::InvalidEmailAddressError)
280            }
281            let session = self.get_session()?;
282            let mut user = self.users.get_by_id(session.id).await?;
283            user.email = email;
284            self.users.modify(&user).await?;
285        } else {
286            throw!(Error::UnauthorizedError)
287        }
288    }
289
290    /// This method is useful when the function returns a Result type.
291    /// It is intended to be used primarily
292    /// with the `?` operator.
293    /// ```
294    /// # fn func(auth: rocket_auth::Auth) -> Result<(), rocket_auth::Error> {
295    /// auth.get_session()?;
296    /// # Ok(())
297    /// # }
298    /// ```
299    pub fn get_session(&self) -> Result<&Session> {
300        let session = self.session.as_ref().ok_or(Error::UnauthenticatedError)?;
301        Ok(session)
302    }
303}