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,21 +1,27 @@
pub mod extractors;
mod routes;
use axum::Router;
use domain::{BroadcastPort, ClientRegistry, ConfigRepository, EventPublisher, WidgetStateReader};
use domain::{
AuthPort, BroadcastPort, ClientRegistry, ConfigRepository, EventPublisher, PasswordHashPort,
WidgetStateReader,
};
use std::sync::Arc;
use tower_http::cors::CorsLayer;
use tower_http::services::{ServeDir, ServeFile};
pub struct AppState<C, E, W, B, R> {
pub struct AppState<C, E, W, B, R, A, H> {
pub config: Arc<C>,
pub events: Arc<E>,
pub widget_states: Arc<W>,
pub broadcaster: Arc<B>,
pub clients: Arc<R>,
pub auth: Arc<A>,
pub hasher: Arc<H>,
pub spa_dir: Option<String>,
}
impl<C, E, W, B, R> Clone for AppState<C, E, W, B, R> {
impl<C, E, W, B, R, A, H> Clone for AppState<C, E, W, B, R, A, H> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
@@ -23,12 +29,14 @@ impl<C, E, W, B, R> Clone for AppState<C, E, W, B, R> {
widget_states: self.widget_states.clone(),
broadcaster: self.broadcaster.clone(),
clients: self.clients.clone(),
auth: self.auth.clone(),
hasher: self.hasher.clone(),
spa_dir: self.spa_dir.clone(),
}
}
}
pub fn router<C, E, W, B, R>(state: AppState<C, E, W, B, R>) -> Router
pub fn router<C, E, W, B, R, A, H>(state: AppState<C, E, W, B, R, A, H>) -> Router
where
C: ConfigRepository + Send + Sync + 'static,
C::Error: std::fmt::Debug + Send,
@@ -38,6 +46,8 @@ where
B: BroadcastPort + Send + Sync + 'static,
B::Error: std::fmt::Debug + Send,
R: ClientRegistry + Send + Sync + 'static,
A: AuthPort + Send + Sync + 'static,
H: PasswordHashPort + Send + Sync + 'static,
{
let spa_dir = state.spa_dir.clone();
@@ -54,9 +64,9 @@ where
}
}
pub async fn serve<C, E, W, B, R>(
pub async fn serve<C, E, W, B, R, A, H>(
addr: &str,
state: AppState<C, E, W, B, R>,
state: AppState<C, E, W, B, R, A, H>,
) -> Result<(), std::io::Error>
where
C: ConfigRepository + Send + Sync + 'static,
@@ -67,6 +77,8 @@ where
B: BroadcastPort + Send + Sync + 'static,
B::Error: std::fmt::Debug + Send,
R: ClientRegistry + Send + Sync + 'static,
A: AuthPort + Send + Sync + 'static,
H: PasswordHashPort + Send + Sync + 'static,
{
let app = router(state);
let listener = tokio::net::TcpListener::bind(addr).await?;