util: refactor password API
This commit is contained in:
parent
ac6d756acb
commit
96392afc39
3 changed files with 28 additions and 26 deletions
src
|
@ -5,7 +5,7 @@ use crate::core::*;
|
||||||
use crate::model::Account;
|
use crate::model::Account;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::util::password::ClearPassword;
|
use crate::util::password::ClearPassword;
|
||||||
use crate::util::{password, token};
|
use crate::util::token;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AuthRequest {
|
struct AuthRequest {
|
||||||
|
@ -32,7 +32,7 @@ async fn auth(body: web::Json<AuthRequest>, state: AppState) -> Result<HttpRespo
|
||||||
Error::NotFound => Error::BadCredentials,
|
Error::NotFound => Error::BadCredentials,
|
||||||
e => e,
|
e => e,
|
||||||
})?;
|
})?;
|
||||||
password::verify(&body.password, &user.password)?;
|
user.password.verify(&body.password)?;
|
||||||
let account = state.repo.accounts.by_id(user.account_id).await?;
|
let account = state.repo.accounts.by_id(user.account_id).await?;
|
||||||
let token = token::issue(&state, &account)?;
|
let token = token::issue(&state, &account)?;
|
||||||
info!(target: "auth", "Successful login for user {}", &account.name);
|
info!(target: "auth", "Successful login for user {}", &account.name);
|
||||||
|
|
|
@ -43,7 +43,7 @@ async fn signup(data: web::Form<SignupData>, state: AppState) -> Result<HttpResp
|
||||||
.create(Insane::from(NewUser {
|
.create(Insane::from(NewUser {
|
||||||
account_id: account.id,
|
account_id: account.id,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: password::hash(&data.password),
|
password: data.password.hash(),
|
||||||
locale: data.locale,
|
locale: data.locale,
|
||||||
reason: data.reason,
|
reason: data.reason,
|
||||||
private_key: privkey,
|
private_key: privkey,
|
||||||
|
|
|
@ -6,35 +6,32 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::util::validate::{FieldResultBuilder, ValidateField};
|
use crate::util::validate::{FieldResultBuilder, ValidateField};
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
#[sqlx(transparent)]
|
|
||||||
pub struct ClearPassword(String);
|
pub struct ClearPassword(String);
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Clone)]
|
#[derive(sqlx::Type, Serialize, Deserialize, Clone)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct HashedPassword(String);
|
pub struct HashedPassword(String);
|
||||||
|
|
||||||
pub fn hash(clear: &ClearPassword) -> HashedPassword {
|
impl HashedPassword {
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
pub fn verify(&self, clear: &ClearPassword) -> Result<()> {
|
||||||
let argon2 = Argon2::default();
|
let clear = clear.0.as_str();
|
||||||
HashedPassword(
|
let hash = PasswordHash::new(self.0.as_str()).expect("Corrupt password hash in database");
|
||||||
|
let argon2 = Argon2::default();
|
||||||
argon2
|
argon2
|
||||||
.hash_password(clear.0.as_bytes(), &salt)
|
.verify_password(clear.as_bytes(), &hash)
|
||||||
.unwrap()
|
.map_err(|_| Error::BadCredentials)
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(clear: &ClearPassword, hash: &HashedPassword) -> Result<()> {
|
|
||||||
let (clear, hash) = (clear.0.as_str(), hash.0.as_str());
|
|
||||||
let parsed_hash = PasswordHash::new(hash).unwrap();
|
|
||||||
match Argon2::default().verify_password(clear.as_bytes(), &parsed_hash) {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err(Error::BadCredentials),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClearPassword {
|
impl ClearPassword {
|
||||||
|
pub fn hash(self) -> HashedPassword {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let hash = argon2.hash_password(self.0.as_bytes(), &salt).unwrap();
|
||||||
|
HashedPassword(hash.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
@ -89,13 +86,18 @@ impl<'a> ValidateField<'a> for ClearPassword {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::util::password::{hash, verify};
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_hashes() {
|
fn validate_hashes() {
|
||||||
let h = hash(&String::from("asdf").into());
|
let clear = ClearPassword::from("asdf".to_owned());
|
||||||
assert!(verify(&String::from("asdf").into(), &h).is_ok());
|
let hash = clear.clone().hash();
|
||||||
assert!(verify(&String::from("fdsa").into(), &h).is_err());
|
assert!(hash.verify(&clear).is_ok());
|
||||||
assert!(verify(&String::from("asdf\0").into(), &h).is_err());
|
assert!(hash.verify(&clear_from("fdsa")).is_err());
|
||||||
|
assert!(hash.verify(&clear_from("asdf\0")).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_from(s: &str) -> ClearPassword {
|
||||||
|
ClearPassword::from(s.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue