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,9 +1,11 @@
mod data_source;
mod layout_preset;
mod user;
mod widget_config;
pub use data_source::{
DataSource, DataSourceConfig, DataSourceId, DataSourceType, DataSourceValidationError,
};
pub use layout_preset::{LayoutPreset, LayoutPresetId};
pub use user::{User, UserId};
pub use widget_config::{WidgetConfig, WidgetId};

View File

@@ -0,0 +1,8 @@
pub type UserId = u32;
#[derive(Debug, Clone)]
pub struct User {
pub id: UserId,
pub username: String,
pub password_hash: String,
}

View File

@@ -7,12 +7,12 @@ pub mod value_objects;
pub use entities::{
DataSource, DataSourceConfig, DataSourceId, DataSourceType, DataSourceValidationError,
LayoutPreset, LayoutPresetId, WidgetConfig, WidgetId,
LayoutPreset, LayoutPresetId, User, UserId, WidgetConfig, WidgetId,
};
pub use events::DomainEvent;
pub use ports::{
BroadcastPort, ClientRegistry, ConfigRepository, ConnectedClient, DataSourcePort,
EventPublisher, WidgetStateReader,
AuthPort, BroadcastPort, ClientRegistry, ConfigRepository, ConnectedClient, DataSourcePort,
EventPublisher, PasswordHashPort, SecretStore, WidgetStateReader,
};
pub use value_objects::{
ContainerNode, Direction, DisplayHint, KeyMapping, Layout, LayoutChild, LayoutNode,

View File

@@ -0,0 +1,12 @@
use crate::entities::UserId;
use std::future::Future;
pub trait AuthPort {
fn generate_token(&self, user_id: UserId) -> String;
fn validate_token(&self, token: &str) -> Option<UserId>;
}
pub trait PasswordHashPort {
fn hash(&self, plain: &str) -> impl Future<Output = Result<String, String>> + Send;
fn verify(&self, plain: &str, hash: &str) -> impl Future<Output = Result<bool, String>> + Send;
}

View File

@@ -1,5 +1,5 @@
use crate::entities::{
DataSource, DataSourceId, LayoutPreset, LayoutPresetId, WidgetConfig, WidgetId,
DataSource, DataSourceId, LayoutPreset, LayoutPresetId, User, WidgetConfig, WidgetId,
};
use crate::value_objects::Layout;
use std::future::Future;
@@ -50,4 +50,11 @@ pub trait ConfigRepository {
&self,
id: LayoutPresetId,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn get_user_by_username(
&self,
username: &str,
) -> impl Future<Output = Result<Option<User>, Self::Error>> + Send;
fn save_user(&self, user: &User) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn count_users(&self) -> impl Future<Output = Result<u32, Self::Error>> + Send;
}

View File

@@ -1,13 +1,17 @@
mod auth;
mod broadcast;
mod client_registry;
mod config_repository;
mod data_source_port;
mod event;
mod secret_store;
mod widget_state_reader;
pub use auth::{AuthPort, PasswordHashPort};
pub use broadcast::BroadcastPort;
pub use client_registry::{ClientRegistry, ConnectedClient};
pub use config_repository::ConfigRepository;
pub use data_source_port::DataSourcePort;
pub use event::EventPublisher;
pub use secret_store::SecretStore;
pub use widget_state_reader::WidgetStateReader;

View File

@@ -0,0 +1,4 @@
pub trait SecretStore {
fn encrypt(&self, plaintext: &str) -> String;
fn decrypt(&self, ciphertext: &str) -> String;
}