142 lines
3.6 KiB
Rust
142 lines
3.6 KiB
Rust
use {
|
|
crate::{db, db::Database},
|
|
argon2::{
|
|
password_hash::{
|
|
rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
|
},
|
|
Argon2,
|
|
},
|
|
std::ops::Deref,
|
|
tracing::{error, warn},
|
|
};
|
|
|
|
mod jwt;
|
|
|
|
pub use jwt::{authenticate_user_with_jwt, create_jwt_for_user, Error as JwtError, ParsedJwt};
|
|
|
|
#[derive(Debug)]
|
|
pub enum AuthenticationError {
|
|
DatabaseError(db::Error),
|
|
HashError(argon2::password_hash::Error),
|
|
}
|
|
|
|
impl std::fmt::Display for AuthenticationError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
AuthenticationError::HashError(e) => write!(f, "{}", e),
|
|
AuthenticationError::DatabaseError(e) => {
|
|
write!(f, "Could not get password hash from database: {}", e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for AuthenticationError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
AuthenticationError::HashError(_) => None,
|
|
AuthenticationError::DatabaseError(e) => Some(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<db::Error> for AuthenticationError {
|
|
fn from(value: db::Error) -> Self {
|
|
warn!(details = value.to_string(), "Database error");
|
|
AuthenticationError::DatabaseError(value)
|
|
}
|
|
}
|
|
|
|
impl From<argon2::password_hash::Error> for AuthenticationError {
|
|
fn from(value: argon2::password_hash::Error) -> Self {
|
|
error!(details = value.to_string(), "Error hashing password.");
|
|
AuthenticationError::HashError(value)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AuthenticatedUser(db::User);
|
|
|
|
impl Deref for AuthenticatedUser {
|
|
type Target = db::User;
|
|
|
|
fn deref(&self) -> &db::User {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthenticatedAdminUser(db::User);
|
|
|
|
impl Deref for AuthenticatedAdminUser {
|
|
type Target = db::User;
|
|
|
|
fn deref(&self) -> &db::User {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
pub struct Password {
|
|
hash: String,
|
|
}
|
|
|
|
impl Password {
|
|
#[tracing::instrument]
|
|
pub fn new(password: &str) -> Result<Password, AuthenticationError> {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let argon2 = Argon2::default();
|
|
Ok(Password {
|
|
hash: argon2
|
|
.hash_password(password.as_bytes(), &salt)?
|
|
.to_string(),
|
|
})
|
|
}
|
|
|
|
pub fn check(&self, password: &str) -> Result<bool, AuthenticationError> {
|
|
let hash = PasswordHash::new(&self.hash)?;
|
|
Ok(Argon2::default()
|
|
.verify_password(password.as_bytes(), &hash)
|
|
.is_ok())
|
|
}
|
|
}
|
|
|
|
impl From<db::PasswordHash> for Password {
|
|
fn from(password: db::PasswordHash) -> Self {
|
|
Password {
|
|
hash: password.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Password> for db::PasswordHash {
|
|
fn from(password: Password) -> Self {
|
|
db::PasswordHash(password.hash)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument]
|
|
pub async fn authenticate_user_with_password<D: Database>(
|
|
db: &D,
|
|
user: db::User,
|
|
supplied_password: &str,
|
|
) -> Result<Option<AuthenticatedUser>, AuthenticationError> {
|
|
let password: Password = db.get_password_for_user(&user).await?.into();
|
|
Ok(if password.check(supplied_password)? {
|
|
Some(AuthenticatedUser(user))
|
|
} else {
|
|
None
|
|
})
|
|
}
|
|
|
|
#[tracing::instrument]
|
|
pub async fn check_if_user_is_admin<D: Database>(
|
|
db: &D,
|
|
user: &AuthenticatedUser,
|
|
) -> Result<Option<AuthenticatedAdminUser>, db::Error> {
|
|
if db.is_user_admin(user).await? {
|
|
Ok(Some(AuthenticatedAdminUser(user.0.clone())))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|