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