BasicOIDC/src/data/user.rs

192 lines
5.2 KiB
Rust

use crate::data::client::ClientID;
use crate::data::entity_manager::EntityManager;
use crate::data::login_redirect::LoginRedirect;
use crate::data::totp_key::TotpKey;
use crate::data::webauthn_manager::WebauthnPubKey;
use crate::utils::err::Res;
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct UserID(pub String);
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FactorID(pub String);
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum TwoFactorType {
TOTP(TotpKey),
WEBAUTHN(WebauthnPubKey),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TwoFactor {
pub id: FactorID,
pub name: String,
pub kind: TwoFactorType,
}
impl TwoFactor {
pub fn type_str(&self) -> &'static str {
match self.kind {
TwoFactorType::TOTP(_) => "Authenticator app",
TwoFactorType::WEBAUTHN(_) => "Security key",
}
}
pub fn login_url(&self, redirect_uri: &LoginRedirect) -> String {
match self.kind {
TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect={}",
self.id.0, redirect_uri.get_encoded()),
TwoFactorType::WEBAUTHN(_) => format!("/2fa_webauthn?id={}&redirect={}",
self.id.0, redirect_uri.get_encoded()),
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct User {
pub uid: UserID,
pub first_name: String,
pub last_name: String,
pub username: String,
pub email: String,
pub password: String,
pub need_reset_password: bool,
pub enabled: bool,
pub admin: bool,
/// 2FA
#[serde(default)]
pub two_factor: Vec<TwoFactor>,
/// None = all services
/// Some([]) = no service
pub authorized_clients: Option<Vec<ClientID>>,
}
impl User {
pub fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
pub fn can_access_app(&self, id: &ClientID) -> bool {
match &self.authorized_clients {
None => true,
Some(c) => c.contains(id)
}
}
pub fn verify_password<P: AsRef<[u8]>>(&self, pass: P) -> bool {
verify_password(pass, &self.password)
}
pub fn has_two_factor(&self) -> bool {
!self.two_factor.is_empty()
}
pub fn add_factor(&mut self, factor: TwoFactor) {
self.two_factor.push(factor);
}
pub fn remove_factor(&mut self, factor_id: FactorID) {
self.two_factor.retain(|f| f.id != factor_id);
}
pub fn find_factor(&self, factor_id: &FactorID) -> Option<&TwoFactor> {
self.two_factor.iter().find(|f| f.id.eq(factor_id))
}
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.uid.eq(&other.uid)
}
}
impl Eq for User {}
impl Default for User {
fn default() -> Self {
Self {
uid: UserID(uuid::Uuid::new_v4().to_string()),
first_name: "".to_string(),
last_name: "".to_string(),
username: "".to_string(),
email: "".to_string(),
password: "".to_string(),
need_reset_password: false,
enabled: true,
admin: false,
two_factor: vec![],
authorized_clients: Some(Vec::new()),
}
}
}
pub fn hash_password<P: AsRef<[u8]>>(pwd: P) -> Res<String> {
Ok(bcrypt::hash(pwd, bcrypt::DEFAULT_COST)?)
}
pub fn verify_password<P: AsRef<[u8]>>(pwd: P, hash: &str) -> bool {
match bcrypt::verify(pwd, hash) {
Ok(r) => r,
Err(e) => {
log::warn!("Failed to verify password! {:?}", e);
false
}
}
}
impl EntityManager<User> {
pub fn find_by_username_or_email(&self, u: &str) -> Option<User> {
for entry in self.iter() {
if entry.username.eq(u) || entry.email.eq(u) {
return Some(entry.clone());
}
}
None
}
pub fn find_by_user_id(&self, id: &UserID) -> Option<User> {
for entry in self.iter() {
if entry.uid.eq(id) {
return Some(entry.clone());
}
}
None
}
/// Update user information
fn update_user<F>(&mut self, id: &UserID, update: F) -> bool
where
F: FnOnce(User) -> User,
{
let user = match self.find_by_user_id(id) {
None => return false,
Some(user) => user,
};
if let Err(e) = self.replace_entries(|u| u.uid.eq(id), &update(user)) {
log::error!("Failed to update user information! {:?}", e);
return false;
}
true
}
pub fn change_user_password(&mut self, id: &UserID, password: &str, temporary: bool) -> bool {
let new_hash = match hash_password(password) {
Ok(h) => h,
Err(e) => {
log::error!("Failed to hash user password! {}", e);
return false;
}
};
self.update_user(id, |mut user| {
user.password = new_hash;
user.need_reset_password = temporary;
user
})
}
}