Skip to content
This repository was archived by the owner on Aug 5, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
Cargo.lock
examples/database.db
.vscode
.DS_Store
.DS_Store
database.db
38 changes: 19 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rocket_auth"
version = "0.4.0"
version = "0.5.0"
authors = ["tvallotton@uc.cl"]
edition = "2018"
license = "MIT or Apache-2.0"
Expand Down Expand Up @@ -29,31 +29,31 @@ optional = true


[dependencies]
rand = "0.8.5"
rust-argon2 = "1.0.0"
lazy_static = "1.4.0"
regex = "1.5.6"
serde_json = "1.0.82"
chashmap = "2.2.2"
thiserror = "1.0.31"
async-trait = "0.1.56"
fehler = "1.0.0"
chrono = "0.4.19"
validator = { version = "0.15.0", features = ["derive"] }
futures= "0.3.21"
rand = ">=0.8.5"
rust-argon2 = ">=1.0.0"
lazy_static = ">=1.4.0"
regex = ">=1.5.6"
serde_json = ">=1.0.82"
chashmap = ">=2.2.2"
thiserror = ">=1.0.31"
async-trait = ">=0.1.56"
fehler = ">=1.0.0"
chrono = ">=0.4.19"
validator = { version = ">=0.15.0", features = ["derive"] }
futures= ">=0.3.21"


[dependencies.sqlx]
version = "0.6.0"
version = ">=0.6.0"
optional = true


[dependencies.rocket]
version = "0.5.0-rc.2"
version = ">=0.5.0"
features = ["secrets"]

[dependencies.serde]
version = "1.0.138"
version = ">=1.0.138"
features = ["derive"]

[dependencies.tokio-postgres]
Expand All @@ -75,7 +75,7 @@ tokio-postgres= "0.7.6"


[dev-dependencies.rocket]
version = "0.5.0-rc.2"
version = ">=0.5.0"
features = ["secrets", "json"]

[dev-dependencies.redis]
Expand All @@ -84,12 +84,12 @@ features = ["aio", "tokio-comp"]


[dev-dependencies.rocket_dyn_templates]
version = "0.1.0-rc.2"
version = ">=0.1.0"
features = ["tera"]


[dev-dependencies.sqlx]
version = "0.6.0"
version = ">=0.6.0"
features = ["runtime-tokio-rustls"]

[dev-dependencies.rocket_auth]
Expand Down
Binary file added database.db
Binary file not shown.
3 changes: 2 additions & 1 deletion src/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::prelude::*;
use rocket::http::{CookieJar, Status};
use rocket::request::{FromRequest, Outcome, Request};
use serde_json::from_str;
use crate::error;

/// The Session guard can be used to retrieve user session data.
/// Unlike `User`, using session does not verify that the session data is
Expand Down Expand Up @@ -34,7 +35,7 @@ impl<'r> FromRequest<'r> for Session {
if let Some(session) = get_session(cookies) {
Outcome::Success(session)
} else {
Outcome::Failure((Status::Unauthorized, Error::UnauthorizedError))
Outcome::Error((Status::Unauthorized, error::Error::UnauthorizedError))
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions src/forms/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::prelude::*;
use validator::Validate;


/// The `Login` form is used along with the [`Auth`] guard to authenticate users.
Expand All @@ -14,12 +15,7 @@ pub struct Login {
pub struct Signup {
#[validate(email)]
pub email: String,
#[validate(
custom = "is_long",
custom = "has_number",
custom = "has_lowercase",
custom = "has_uppercase"
)]
#[validate(length(min=8),custom(function=is_secure))]
pub(crate) password: String,
}
impl Debug for Signup {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//!
//!
//! To use `rocket_auth` include it as a dependency in your Cargo.toml file:
//! ```
//! ```toml
//! [dependencies.rocket_auth]
//! version = "0.4.0"
//! features = ["sqlx-sqlite"]
Expand Down
54 changes: 44 additions & 10 deletions src/user/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ use rocket::Request;
use rocket::State;
use serde_json::json;
use std::time::Duration;
use regex::Regex;



/// Validates an email address (helper function).
pub fn validate_email(email: &String) -> bool {
let expr = Regex::new("^[\\w\\.-]+@[\\w\\.-]+\\.[a-zA-Z]{2,6}$");

if let Ok(reg_ex) = expr {
return reg_ex.is_match(&email)
} else {
return false
}

}

/// The [`Auth`] guard allows to log in, log out, sign up, modify, and delete the currently (un)authenticated user.
/// For more information see [`Auth`].
Expand Down Expand Up @@ -64,7 +79,7 @@ impl<'r> FromRequest<'r> for Auth<'r> {
let users: &State<Users> = if let Outcome::Success(users) = req.guard().await {
users
} else {
return Outcome::Failure((Status::InternalServerError, Error::UnmanagedStateError));
return Outcome::Error((Status::InternalServerError, Error::UnmanagedStateError));
};

Outcome::Success(Auth {
Expand Down Expand Up @@ -222,7 +237,7 @@ impl<'a> Auth<'a> {
pub fn logout(&self) {
let session = self.get_session()?;
self.users.logout(session)?;
self.cookies.remove_private(Cookie::named("rocket_auth"));
self.cookies.remove_private(Cookie::build("rocket_auth"));
}
/// Deletes the account of the currently authenticated user.
/// ```rust
Expand All @@ -238,7 +253,7 @@ impl<'a> Auth<'a> {
if self.is_auth() {
let session = self.get_session()?;
self.users.delete(session.id).await?;
self.cookies.remove_private(Cookie::named("rocket_auth"));
self.cookies.remove_private("rocket_auth");
} else {
throw!(Error::UnauthenticatedError)
}
Expand All @@ -253,13 +268,14 @@ impl<'a> Auth<'a> {
/// auth.change_password("new password");
/// # }
/// ```
#[throws(Error)]
pub async fn change_password(&self, password: &str) {
pub async fn change_password(&self, password: &str) -> Result<(), Box<dyn std::error::Error>> {
if self.is_auth() {
let session = self.get_session()?;
let mut user = self.users.get_by_id(session.id).await?;
user.set_password(password)?;
self.users.modify(&user).await?;

Ok(())
} else {
throw!(Error::UnauthorizedError)
}
Expand All @@ -272,18 +288,18 @@ impl<'a> Auth<'a> {
/// auth.change_email("new@email.com".into());
/// # }
/// ```
#[throws(Error)]
pub async fn change_email(&self, email: String) {
pub async fn change_email(&self, email: String) -> Result<(), Error> {
if self.is_auth() {
if !validator::validate_email(&email) {
throw!(Error::InvalidEmailAddressError)
if !validate_email(&email) {
return Err(Error::InvalidEmailAddressError)
}
let session = self.get_session()?;
let mut user = self.users.get_by_id(session.id).await?;
user.email = email.to_lowercase();
self.users.modify(&user).await?;
return Ok(())
} else {
throw!(Error::UnauthorizedError)
return Err(Error::UnauthorizedError)
}
}

Expand Down Expand Up @@ -316,3 +332,21 @@ impl<'a> Auth<'a> {
}
}
}



#[cfg(test)]
mod test {

use super::validate_email;


#[test]
fn test_validate_email() {

let good_mail = String::from("some.example@gmail.com");
let bad_mail = String::from("@fak,.r");
assert!(validate_email(&good_mail));
assert!(!(validate_email(&bad_mail)));
}
}
45 changes: 23 additions & 22 deletions src/user/user_impl.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::auth::Auth;
use super::auth::{Auth, validate_email};
use super::rand_string;

use crate::prelude::*;
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome, Request};
use crate::error;

impl User {
/// This method allows to reset the password of a user.
Expand All @@ -14,39 +15,39 @@ impl User {
/// This function will fail in case the password is not secure enough.
///
/// ```rust
/// # use rocket::{State, post};
/// # use rocket_auth::{Error, Users};
/// use rocket::{State, post};
/// use rocket_auth::{Error, Users, User};
/// #[post("/reset-password/<new_password>")]
/// async fn reset_password(mut user: User, users: &State<Users>, new_password: String) -> Result<(), Error> {
/// user.set_password(&new_password);
/// users.modify(&user).await?;
/// Ok(())
/// }
/// ```
#[throws(Error)]
pub fn set_password(&mut self, new: &str) {
pub fn set_password(&mut self, new: &str) -> Result<(), Box<dyn std::error::Error>> {
crate::forms::is_secure(new)?;
let password = new.as_bytes();
let salt = rand_string(10);
let config = argon2::Config::default();
let hash = argon2::hash_encoded(password, salt.as_bytes(), &config).unwrap();
let hash = argon2::hash_encoded(password, salt.as_bytes(), &config)?;
self.password = hash;
Ok(())
}

/// Compares the password of the currently authenticated user with a another password.
/// Useful for checking password before resetting email/password.
/// To avoid bruteforcing this function should not be directly accessible from a route.
/// Additionally, it is good to implement rate limiting on routes using this function.
#[throws(Error)]
pub fn compare_password(&self, password: &str) -> bool {
verify_encoded(&self.password, password.as_bytes())?

pub fn compare_password(&self, password: &str) -> Result<bool, argon2::Error> {
verify_encoded(&self.password, password.as_bytes())
}

/// This is an accessor function for the private `id` field.
/// This field is private, so that it is not modified by accident when updating a user.
/// ```rust
/// # use rocket::{State, get};
/// # use rocket_auth::{Error, User};
/// use rocket::{State, get};
/// use rocket_auth::{Error, User};
/// #[get("/show-my-id")]
/// fn show_my_id(user: User) -> String {
/// format!("Your user_id is: {}", user.id())
Expand All @@ -58,8 +59,8 @@ impl User {
/// This is an accessor field for the private `email` field.
/// This field is private so an email cannot be updated without checking whether it is valid.
/// ```rust
/// # use rocket::{State, get};
/// # use rocket_auth::{Error, User};
/// use rocket::{State, get};
/// use rocket_auth::{Error, User};
/// #[get("/show-my-email")]
/// fn show_my_email(user: User) -> String {
/// format!("Your user_id is: {}", user.email())
Expand All @@ -78,15 +79,15 @@ impl User {
/// #[get("/set-email/<email>")]
/// async fn set_email(email: String, auth: Auth<'_>) -> Result<String, Error> {
/// let mut user = auth.get_user().await.unwrap();
/// user.set_email(&email)?;
/// user.set_email(email)?;
/// auth.users.modify(&user).await?;
/// Ok("Your user email was changed".into())
/// }
/// ```
#[throws(Error)]
pub fn set_email(&mut self, email: &str) {
if validator::validate_email(email) {
pub fn set_email(&mut self, email: String) -> Result<(), Error>{
if validate_email(&email) {
self.email = email.to_lowercase();
Ok(())
} else {
throw!(Error::InvalidEmailAddressError)
}
Expand All @@ -113,13 +114,13 @@ impl<'r> FromRequest<'r> for User {
let guard = request.guard().await;
let auth: Auth = match guard {
Success(auth) => auth,
Failure(x) => return Failure(x),
Error(x) => return Error(x),
Forward(x) => return Forward(x),
};
if let Some(user) = auth.get_user().await {
Outcome::Success(user)
} else {
Outcome::Failure((Status::Unauthorized, Error::UnauthorizedError))
Outcome::Error((Status::Unauthorized, error::Error::UserNotFoundError))
}
}
}
Expand All @@ -132,19 +133,19 @@ impl<'r> FromRequest<'r> for AdminUser {
let guard = request.guard().await;
let auth: Auth = match guard {
Success(auth) => auth,
Failure(x) => return Failure(x),
Error(x) => return Error(x),
Forward(x) => return Forward(x),
};
if let Some(user) = auth.get_user().await {
if user.is_admin {
return Outcome::Success(AdminUser(user));
}
}
Outcome::Failure((Status::Unauthorized, Error::UnauthorizedError))
Outcome::Error((Status::Unauthorized, error::Error::UnauthorizedError))
}
}

use std::ops::*;
use std::{ops::*, result};
use argon2::verify_encoded;

impl Deref for AdminUser {
Expand Down
Loading