Skip to content

Salt to user table #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
19 changes: 10 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ serde_json = "1.0"
uuid = { version = "0.7", features = ["serde", "v4"] }
validator = "0.8.0"
validator_derive = "0.8.0"
rand = "0.7.3"

[dev-dependencies]
actix-http-test = "0.2.0"
Expand Down
3 changes: 3 additions & 0 deletions migrations/2020-04-27-193614_add_user_salt/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE users
DROP COLUMN salt;
3 changes: 3 additions & 0 deletions migrations/2020-04-27-193614_add_user_salt/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE users
ADD COLUMN salt VARCHAR(36) NOT NULL DEFAULT '' AFTER password;
44 changes: 39 additions & 5 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ pub fn decode_jwt(token: &str) -> Result<PrivateClaim, ApiError> {
///
/// Uses the argon2i algorithm.
/// auth_salt is environment-configured.
pub fn hash(password: &str) -> String {
argon2i_simple(&password, &CONFIG.auth_salt)
pub fn hash(password: &str, salt: &String) -> String {
let masked = mask_str(&salt, &CONFIG.auth_salt);
argon2i_simple(&password, &masked)
.iter()
.map(|b| format!("{:02x}", b))
.collect()
Expand All @@ -63,23 +64,47 @@ pub fn get_identity_service() -> IdentityService<CookieIdentityPolicy> {
)
}


/// Mask one string with another. Used for combining salts.
fn mask_str(str: &String, mask : &String) -> String{
let mut strb = str.clone().into_bytes();
let maskb = mask.clone().into_bytes();
let str_len = strb.len();
let mask_len = maskb.len();
let mut i = 0;
let mut m = 0;
while i < str_len{
if m >= mask_len {
m = 0;
}
strb[i] = (strb[i].wrapping_add(maskb[m])) % 128;
i += 1;
m+= 1;
}
return String::from_utf8(strb).unwrap();
}

#[cfg(test)]
pub mod tests {
use super::*;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
static EMAIL: &str = "[email protected]";

#[test]
fn it_hashes_a_password() {
let password = "password";
let hashed = hash(password);
let salt = thread_rng().sample_iter(&Alphanumeric).take(32).collect::<String>();
let hashed = hash(password, &salt);
assert_ne!(password, hashed);
}

#[test]
fn it_matches_2_hashed_passwords() {
let password = "password";
let hashed = hash(password);
let hashed_again = hash(password);
let salt = thread_rng().sample_iter(&Alphanumeric).take(32).collect::<String>();
let hashed = hash(password, &salt);
let hashed_again = hash(password, &salt);
assert_eq!(hashed, hashed_again);
}

Expand All @@ -97,4 +122,13 @@ pub mod tests {
let decoded = decode_jwt(&jwt).unwrap();
assert_eq!(private_claim, decoded);
}


#[test]
fn it_masks_a_string() {
let salt = "salt1salt2salt3".to_string();
let mask = "mask52632".to_string();
let masked = mask_str(&salt, &mask);
assert_ne!(masked, "".to_string());
}
}
7 changes: 3 additions & 4 deletions src/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::auth::{create_jwt, hash, PrivateClaim};
use crate::auth::{create_jwt, PrivateClaim};
use crate::database::PoolType;
use crate::errors::ApiError;
use crate::handlers::user::UserResponse;
Expand Down Expand Up @@ -31,9 +31,8 @@ pub async fn login(
) -> Result<Json<UserResponse>, ApiError> {
validate(&params)?;

// Validate that the email + hashed password matches
let hashed = hash(&params.password);
let user = block(move || find_by_auth(&pool, &params.email, &hashed)).await?;
// Validate that the email + password matches
let user = block(move || find_by_auth(&pool, &params.email, &params.password)).await?;

// Create a JWT
let private_claim = PrivateClaim::new(user.id, user.email.clone());
Expand Down
18 changes: 14 additions & 4 deletions src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use crate::database::PoolType;
use crate::errors::ApiError;
use crate::handlers::user::{UserResponse, UsersResponse};
use crate::schema::users;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use chrono::{NaiveDateTime, Utc};
use diesel::prelude::*;
use uuid::Uuid;
Expand All @@ -14,6 +16,7 @@ pub struct User {
pub last_name: String,
pub email: String,
pub password: String,
pub salt: String,
pub created_by: String,
pub created_at: NaiveDateTime,
pub updated_by: String,
Expand Down Expand Up @@ -78,15 +81,20 @@ pub fn find_by_auth(
user_email: &str,
user_password: &str,
) -> Result<UserResponse, ApiError> {
use crate::schema::users::dsl::{email, password, users};
use crate::schema::users::dsl::{email, users};

let conn = pool.get()?;
let user = users
.filter(email.eq(user_email.to_string()))
.filter(password.eq(user_password.to_string()))
.first::<User>(&conn)
.map_err(|_| ApiError::Unauthorized("Invalid login".into()))?;
Ok(user.into())
let hashed = hash(user_password,&user.salt);
if hashed.eq(&user.password) {
Ok(user.into())
} else {
Err(ApiError::Unauthorized("Invalid login".into()))
}

}

/// Create a new user
Expand Down Expand Up @@ -123,12 +131,14 @@ pub fn delete(pool: &PoolType, user_id: Uuid) -> Result<(), ApiError> {

impl From<NewUser> for User {
fn from(user: NewUser) -> Self {
let salt = thread_rng().sample_iter(&Alphanumeric).take(32).collect::<String>();
User {
id: user.id,
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
password: hash(&user.password),
password: hash(&user.password,&salt),
salt: salt,
created_by: user.created_by,
created_at: Utc::now().naive_utc(),
updated_by: user.updated_by,
Expand Down
1 change: 1 addition & 0 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ table! {
last_name -> Varchar,
email -> Varchar,
password -> Varchar,
salt -> Varchar,
created_by -> Varchar,
created_at -> Timestamp,
updated_by -> Varchar,
Expand Down