diff --git a/common/secubox_core/auth.py b/common/secubox_core/auth.py index c6823c98..0c2026e4 100644 --- a/common/secubox_core/auth.py +++ b/common/secubox_core/auth.py @@ -22,6 +22,22 @@ log = get_logger("auth") # auto_error=False allows us to handle missing token gracefully _bearer = HTTPBearer(auto_error=False) +# Session event callback (set by auth module to record sessions) +_session_callback = None + +def set_session_callback(callback): + """Set callback function for session events: callback(event, username, details)""" + global _session_callback + _session_callback = callback + +def _emit_session_event(event: str, username: str, details: dict = None): + """Emit session event to callback if set.""" + if _session_callback: + try: + _session_callback(event, username, details or {}) + except Exception as e: + log.warning("Session callback error: %s", e) + # ── JWT helpers ──────────────────────────────────────────────────── def _secret() -> str: @@ -111,10 +127,12 @@ async def login(req: LoginRequest): """Endpoint de login — retourne un JWT.""" if not _check_password(req.username, req.password): log.warning("Échec login: %s", req.username) + _emit_session_event("login_failed", req.username, {"reason": "invalid_credentials"}) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Identifiants incorrects", ) token = create_token(req.username) log.info("Login OK: %s", req.username) + _emit_session_event("login_success", req.username, {"expires_in": 86400}) return TokenResponse(access_token=token) diff --git a/packages/secubox-auth/api/main.py b/packages/secubox-auth/api/main.py index 3f3a6e84..c70207fc 100644 --- a/packages/secubox-auth/api/main.py +++ b/packages/secubox-auth/api/main.py @@ -1,7 +1,7 @@ """SecuBox Auth API - OAuth2 + Vouchers + Sessions with Enhanced Monitoring""" from fastapi import FastAPI, APIRouter, Depends, HTTPException, BackgroundTasks from pydantic import BaseModel, Field, field_validator -from secubox_core.auth import router as auth_router, require_jwt, create_token +from secubox_core.auth import router as auth_router, require_jwt, create_token, set_session_callback from secubox_core.config import get_config import json import secrets @@ -194,11 +194,29 @@ async def _periodic_cleanup(): await asyncio.sleep(300) # Every 5 minutes +def _handle_session_event(event: str, username: str, details: dict): + """Handle session events from secubox_core.auth login.""" + _record_event(event, {"username": username, **details}) + # Also create session record for successful logins + if event == "login_success": + sessions = _load(SESSIONS_FILE, []) + sessions.append({ + "id": secrets.token_hex(8), + "username": username, + "created": datetime.now().isoformat(), + "expires": int(time.time()) + details.get("expires_in", 86400), + "type": "jwt" + }) + _save(SESSIONS_FILE, sessions) + + @app.on_event("startup") async def startup(): - """Start background cleanup.""" + """Start background cleanup and register session callback.""" global _cleanup_task _cleanup_task = asyncio.create_task(_periodic_cleanup()) + # Register callback for login events + set_session_callback(_handle_session_event) @app.on_event("shutdown") diff --git a/packages/secubox-hub/www/login.html b/packages/secubox-hub/www/login.html index 54508932..17364b4c 100644 --- a/packages/secubox-hub/www/login.html +++ b/packages/secubox-hub/www/login.html @@ -234,7 +234,7 @@ submitBtn.textContent = 'Authenticating...'; try { - const res = await fetch('/api/v1/hub/auth/login', { + const res = await fetch('/api/v1/auth/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) @@ -268,25 +268,26 @@ // Load auth mode and version info (public endpoint) async function loadPublicInfo() { try { - const res = await fetch('/api/v1/hub/public/info'); + const res = await fetch('/api/v1/hub/health'); if (res.ok) { - const data = await res.json(); - - // Update auth mode - const authModeEl = document.getElementById('auth-mode'); - const authModeText = document.getElementById('auth-mode-text'); - if (data.auth_mode) { - authModeText.textContent = data.auth_mode; - authModeEl.className = 'auth-mode ' + (data.auth_mode.toLowerCase() === 'zkp' ? 'zkp' : 'standard'); - } - - // Update version - if (data.version) { - document.getElementById('version-badge').textContent = 'SecuBox v' + data.version; + const text = await res.text(); + // Only parse if it looks like JSON + if (text.startsWith('{')) { + const data = JSON.parse(text); + // Update version from health endpoint + if (data.version) { + document.getElementById('version-badge').textContent = 'SecuBox v' + data.version; + } + // Check for ZKP auth mode + const authModeEl = document.getElementById('auth-mode'); + const authModeText = document.getElementById('auth-mode-text'); + authModeText.textContent = data.auth_mode || 'ZKP'; + authModeEl.className = 'auth-mode zkp'; } } } catch (e) { // Silently fail - use defaults + console.log('[Login] Info fetch skipped:', e.message); } }