use argon2::PasswordVerifier; use argon2::{ password_hash::{Encoding, PasswordHashString}, Algorithm, Argon2, Params, PasswordHash, Version, }; use serde::de::MapAccess; use serde::{ de::{value, Error, Visitor}, Deserialize, }; use std::{collections::HashMap, fmt, fs::read_to_string}; pub mod basic; pub mod cookie; pub mod openid; struct Credentials { wrong_user: PasswordHashString, hashes: HashMap, } impl Credentials { fn get(&self, username: &str) -> &PasswordHashString { self.hashes.get(username).unwrap_or(&self.wrong_user) } pub fn authentificate(&self, username: &str, password: &str) -> bool { let algo = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default()); let hash = self.get(username); match hash.algorithm().as_str() { "argon2id" => algo .verify_password( password.as_bytes(), &PasswordHash::parse(hash.as_str(), hash.encoding()).unwrap(), ) .is_ok(), "never" => false, _ => false, } } } impl<'de> Deserialize<'de> for Credentials { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct StringOrMap; impl<'de> Visitor<'de> for StringOrMap { type Value = HashMap; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("credentials map or file path") } fn visit_str(self, val: &str) -> Result where E: Error, { let path = String::deserialize(value::StrDeserializer::new(val))?; let c = serde_yml::from_str(&read_to_string(path).map_err(|io| { serde::de::Error::custom(format!("cannot read creds file: {io:?}")) })?) .map_err(|e| serde::de::Error::custom(format!("cannot parse creds file: {e:?}")))?; Ok(c) } fn visit_map(self, val: A) -> Result where A: MapAccess<'de>, { HashMap::deserialize(value::MapAccessDeserializer::new(val)) } } let k = deserializer.deserialize_any(StringOrMap)?; Ok(Credentials { wrong_user: PasswordHashString::parse("$never", Encoding::B64).unwrap(), hashes: k .into_iter() .map(|(k, v)| { let hash = PasswordHash::parse(&v, Encoding::B64) .map_err(|e| { serde::de::Error::custom(format!( "phc string for user {k:?} is invalid: {e:?}" )) })? .serialize(); Ok((k, hash)) }) .try_collect()?, }) } }