use std::fmt::Display; use argon2::{ Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}, }; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::storage::sql::mk_id; /// The definition of an rocie user. #[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] pub(crate) struct User { /// The unique ID for this user. pub(crate) id: UserId, /// The user-displayed name of this user. pub(crate) name: String, /// The hash of the user's password. pub(crate) password_hash: PasswordHash, /// An description of this user. #[schema(nullable = false)] pub(crate) description: Option, } /// This is stored as an PHC password string. /// /// This type corresponds to the string representation of a PHC string as /// described in the [PHC string format specification][1]. /// /// PHC strings have the following format: /// /// ```text /// $[$v=][$=(,=)*][$[$]] /// ``` #[derive(ToSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] pub(crate) struct PasswordHash { value: String, } impl PasswordHash { pub(crate) fn from_db(password_hash: String) -> PasswordHash { Self { value: password_hash, } } pub(crate) fn from_password(password: &str) -> Self { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(password.as_bytes(), &salt) .expect("to not fail") .to_string(); Self { value: password_hash, } } /// Check that self, and the other password have the same hash. pub(crate) fn verify(&self, other: &str) -> bool { let argon2 = Argon2::default(); argon2 .verify_password(other.as_bytes(), &self.as_argon_hash()) .is_ok() } fn as_argon_hash(&self) -> argon2::PasswordHash<'_> { argon2::PasswordHash::new(&self.value) .expect("to be valid, as we are just deserializing a previously serialize value") } } impl Display for PasswordHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.value.fmt(f) } } mk_id!(UserId and UserIdStub);