use std::{borrow::Borrow, sync::Arc};
use dyn_clone::DynClone;
use crate::auth::{config::AuthConfig, errors::AuthError, token::AuthToken, user::UserId};
pub trait AuthProvider: std::fmt::Debug + DynClone + Send + Sync + 'static {
fn authenticate(&self, user: Option<&UserId>, token: &AuthToken) -> Result<(), AuthError>;
fn authorize(&self, user: Option<&UserId>, permission: &'static str) -> Result<(), AuthError>;
}
#[derive(Clone, Debug, Default)]
pub struct NoOpAuthProvider;
impl AuthProvider for NoOpAuthProvider {
fn authenticate(&self, _user: Option<&UserId>, _token: &AuthToken) -> Result<(), AuthError> {
Ok(())
}
fn authorize(
&self,
_user: Option<&UserId>,
_permission: &'static str,
) -> Result<(), AuthError> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct ConfigAuthProvider {
config: Arc<AuthConfig>,
}
impl AuthProvider for ConfigAuthProvider {
fn authenticate(&self, user: Option<&UserId>, token: &AuthToken) -> Result<(), AuthError> {
match (self.config.user(user.map(|u| u.borrow())), token) {
(_, AuthToken::Absent) => Err(AuthError::AuthFailed),
(_, AuthToken::ExternallyVerified) => Ok(()),
(Some(user_cfg), AuthToken::PlainPassword(pwd)) => {
if user_cfg
.password
.as_ref()
.is_some_and(|p| p == pwd.as_str())
{
Ok(())
} else {
Err(AuthError::AuthFailed)
}
}
(None, _) => Err(AuthError::UserNotFound),
}
}
fn authorize(&self, user: Option<&UserId>, permission: &'static str) -> Result<(), AuthError> {
match self.config.user(user.map(|u| u.borrow())) {
Some(user_cfg) => {
for role in &user_cfg.roles {
if let Some(role_cfg) = self.config.roles.get(role) {
if role_cfg.super_user || role_cfg.permissions.contains(permission) {
return Ok(());
}
}
}
Err(AuthError::NoPermission(permission))
}
None => Err(AuthError::UserNotFound),
}
}
}
impl From<AuthConfig> for ConfigAuthProvider {
fn from(value: AuthConfig) -> Self {
Self {
config: Arc::new(value),
}
}
}