add auth system: users, login, JWT, protected routes

Domain: User entity, AuthPort/PasswordHashPort/SecretStore ports.
Adapters: auth (argon2 hashing, JWT tokens), secret-store (env-based),
config-sqlite user repository, http-api auth routes + extractors.
Application: auth_service. SPA: login page, auth client, protected router.
This commit is contained in:
2026-06-19 01:39:42 +02:00
parent 4139330234
commit adda731dc6
41 changed files with 1331 additions and 153 deletions

View File

@@ -1,6 +1,6 @@
use domain::{
ConfigRepository, DataSource, DataSourceId, Layout, LayoutPreset, LayoutPresetId, WidgetConfig,
WidgetId,
ConfigRepository, DataSource, DataSourceId, Layout, LayoutPreset, LayoutPresetId, User,
WidgetConfig, WidgetId,
};
use std::collections::HashMap;
use std::sync::RwLock;
@@ -16,6 +16,7 @@ pub struct MemoryConfigStore {
data_sources: RwLock<HashMap<DataSourceId, DataSource>>,
layout: RwLock<Option<Layout>>,
presets: RwLock<HashMap<LayoutPresetId, LayoutPreset>>,
users: RwLock<Vec<User>>,
}
impl Default for MemoryConfigStore {
@@ -25,6 +26,7 @@ impl Default for MemoryConfigStore {
data_sources: RwLock::new(HashMap::new()),
layout: RwLock::new(None),
presets: RwLock::new(HashMap::new()),
users: RwLock::new(Vec::new()),
}
}
}
@@ -156,4 +158,30 @@ impl ConfigRepository for MemoryConfigStore {
guard.remove(&id);
Ok(())
}
async fn get_user_by_username(&self, username: &str) -> Result<Option<User>, Self::Error> {
let guard = self
.users
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.iter().find(|u| u.username == username).cloned())
}
async fn save_user(&self, user: &User) -> Result<(), Self::Error> {
let mut guard = self
.users
.write()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
guard.retain(|u| u.id != user.id);
guard.push(user.clone());
Ok(())
}
async fn count_users(&self) -> Result<u32, Self::Error> {
let guard = self
.users
.read()
.map_err(|_| MemoryConfigError::LockPoisoned)?;
Ok(guard.len() as u32)
}
}