selene-core 0.9.0-rc.1

Backend for selene-server
Documentation
use argon2::{
    Argon2, PasswordHash, PasswordVerifier,
    password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
};
use serde::{Deserialize, Serialize};

use crate::accounts::PasswordCreationError;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Password(String);

impl Password {
    pub fn new(password: String) -> Result<Self, PasswordCreationError> {
        let len = password.chars().count();
        if !(15..=128).contains(&len) {
            return Err(PasswordCreationError::InvalidLength(len as u32));
        }

        let salt = SaltString::generate(&mut OsRng);
        let password = Argon2::default()
            .hash_password(password.as_bytes(), &salt)
            .expect("Failure requires extreme conditions");

        Ok(Password(password.to_string()))
    }

    pub fn verify(&self, password: impl AsRef<str>) -> bool {
        let password: String = password.as_ref().chars().take(128).collect();
        let password_hash = PasswordHash::new(&self.0).expect("Passwords are PHC compliant");
        match Argon2::default().verify_password(password.as_bytes(), &password_hash) {
            Ok(()) => true,
            Err(argon2::password_hash::Error::Password) => false,
            Err(err) => panic!("Inputs should never cause a panic: {err}"),
        }
    }
}