feat: implement authentication flow
Backend: - AppConfig, AppError, AppState modules for shared infrastructure - JWT creation/verification with HS256 (jsonwebtoken crate) - Session management: SHA-256 token hashing, DB-backed sessions - Auth middleware: AuthUser, RequireHost, RequireAdmin extractors - POST /api/v1/join: name-only registration, 4-digit PIN + bcrypt hash - POST /api/v1/recover: PIN-based recovery with 3-attempt lockout (15 min) - POST /api/v1/admin/login: bcrypt password verification - DELETE /api/v1/session: logout (session invalidation) - Migration 006: user PIN lockout columns (failed_pin_attempts, pin_locked_until) - Models: Event, User (with role enum), Session with all CRUD methods Frontend: - api.ts: typed fetch wrapper with automatic Bearer token injection - auth.ts: JWT/PIN localStorage management with Svelte store - /join: name entry form with PIN display modal and copy button - /recover: name + PIN recovery form with saved PIN pre-fill - /feed: placeholder gallery page with logout - Root layout: auth initialization on mount - Root page: redirect to /join or /feed based on auth state All responses use German language strings as specified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use axum::routing::{delete, post};
|
||||
use axum::Router;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod auth;
|
||||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod models;
|
||||
mod state;
|
||||
|
||||
use config::AppConfig;
|
||||
use state::AppState;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@@ -14,18 +24,22 @@ async fn main() -> Result<()> {
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let database_url =
|
||||
std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
let port: u16 = std::env::var("APP_PORT")
|
||||
.unwrap_or_else(|_| "3000".to_string())
|
||||
.parse()?;
|
||||
let config = AppConfig::from_env()?;
|
||||
let pool = db::create_pool(&config.database_url).await?;
|
||||
let state = AppState::new(pool, config.clone());
|
||||
|
||||
let _pool = db::create_pool(&database_url).await?;
|
||||
let api = Router::new()
|
||||
.route("/api/v1/join", post(auth::handlers::join))
|
||||
.route("/api/v1/recover", post(auth::handlers::recover))
|
||||
.route("/api/v1/admin/login", post(auth::handlers::admin_login))
|
||||
.route("/api/v1/session", delete(auth::handlers::logout));
|
||||
|
||||
let router = axum::Router::new()
|
||||
.route("/health", axum::routing::get(|| async { "ok" }));
|
||||
let router = Router::new()
|
||||
.route("/health", axum::routing::get(|| async { "ok" }))
|
||||
.merge(api)
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await?;
|
||||
let listener = tokio::net::TcpListener::bind(("0.0.0.0", config.app_port)).await?;
|
||||
tracing::info!("listening on {}", listener.local_addr()?);
|
||||
axum::serve(listener, router).await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user