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}