Breaking changes to auth provider, principal store outsourced to new config file

This commit is contained in:
Lennart
2025-02-09 17:19:08 +01:00
parent a71000ccf7
commit 8948589b09
16 changed files with 137 additions and 133 deletions

View File

@@ -25,6 +25,8 @@ pbkdf2 = { workspace = true }
chrono-tz = { workspace = true }
derive_more = { workspace = true }
rustical_xml.workspace = true
toml.workspace = true
tokio.workspace = true
[dev-dependencies]
rstest = { workspace = true }

View File

@@ -1,16 +1,15 @@
pub mod middleware;
pub mod static_user_store;
pub mod toml_user_store;
pub mod user;
mod user_store;
use crate::error::Error;
use async_trait::async_trait;
#[async_trait]
pub trait AuthenticationProvider: 'static {
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
}
pub use middleware::AuthenticationMiddleware;
pub use static_user_store::{StaticUserStore, StaticUserStoreConfig};
pub use toml_user_store::{TomlPrincipalStore, TomlUserStoreConfig};
pub use user::User;
pub use user_store::UserStore;

View File

@@ -1,60 +0,0 @@
use crate::{auth::User, error::Error};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::{AuthenticationProvider, UserStore};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct StaticUserStoreConfig {
pub users: Vec<User>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StaticUserStore {
pub users: HashMap<String, User>,
}
impl StaticUserStore {
pub fn new(config: StaticUserStoreConfig) -> Self {
Self {
users: HashMap::from_iter(config.users.into_iter().map(|user| (user.id.clone(), user))),
}
}
}
#[async_trait]
impl UserStore for StaticUserStore {
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error> {
Ok(self.users.get(id).cloned())
}
}
#[async_trait]
impl AuthenticationProvider for StaticUserStore {
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
let user: User = match self.get_user(user_id).await? {
Some(user) => user,
None => return Ok(None),
};
// Try app tokens first since they are cheaper to calculate
// They can afford less iterations since they can be generated with high entropy
for app_token in &user.app_tokens {
if password_auth::verify_password(token, app_token).is_ok() {
return Ok(Some(user));
}
}
let password = match &user.password {
Some(password) => password,
None => return Ok(None),
};
if password_auth::verify_password(token, password).is_ok() {
return Ok(Some(user));
}
Ok(None)
}
}

View File

@@ -0,0 +1,73 @@
use super::AuthenticationProvider;
use crate::{auth::User, error::Error};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, io};
use tokio::sync::RwLock;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
struct TomlDataModel {
principals: Vec<User>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TomlUserStoreConfig {
pub path: String,
}
#[derive(Debug)]
pub struct TomlPrincipalStore {
pub principals: RwLock<HashMap<String, User>>,
}
#[derive(thiserror::Error, Debug)]
pub enum TomlStoreError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Error parsing users toml: {0}")]
Toml(#[from] toml::de::Error),
}
impl TomlPrincipalStore {
pub fn new(config: TomlUserStoreConfig) -> Result<Self, TomlStoreError> {
let TomlDataModel { principals } = toml::from_str(&fs::read_to_string(&config.path)?)?;
Ok(Self {
principals: RwLock::new(HashMap::from_iter(
principals.into_iter().map(|user| (user.id.clone(), user)),
)),
})
}
}
#[async_trait]
impl AuthenticationProvider for TomlPrincipalStore {
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error> {
Ok(self.principals.read().await.get(id).cloned())
}
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
let user: User = match self.get_principal(user_id).await? {
Some(user) => user,
None => return Ok(None),
};
// Try app tokens first since they are cheaper to calculate
// They can afford less iterations since they can be generated with high entropy
for app_token in &user.app_tokens {
if password_auth::verify_password(token, app_token).is_ok() {
return Ok(Some(user));
}
}
let password = match &user.password {
Some(password) => password,
None => return Ok(None),
};
if password_auth::verify_password(token, password).is_ok() {
return Ok(Some(user));
}
Ok(None)
}
}

View File

@@ -1,7 +0,0 @@
use super::User;
use async_trait::async_trait;
#[async_trait]
pub trait UserStore: 'static {
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error>;
}