192 lines
5.2 KiB
Rust
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
|
|
})
|
|
}
|
|
}
|