mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
feat: implement QR code authentication for tunnel access
Adds ephemeral single-use QR tokens for passwordless tunnel login. Scanning the QR auto-authenticates; bare tunnel URL requires Basic Auth. Backend: - TunnelManager: 60s token rotation, 90s grace, rejection-sampled 6-char base62 short codes, Map-based O(1) lookup, SVG caching, global rate limit - Auth middleware: /q/ bypass, separate qrAuthFailures counter, enhanced AuthSessionRecord with device context (ip, ua, createdAt, method) - Routes: GET /q/:code (consume + cookie + redirect), POST /api/tunnel/qr/ regenerate, POST /api/auth/revoke, updated GET /api/tunnel/qr with cache - SSE: tunnel:qrRotated, tunnel:qrRegenerated, tunnel:qrAuthUsed events - Audit: qr_auth lifecycle log entries Frontend: - Auto-refresh QR via inline SVG in SSE (fallback fetch if absent) - 60s countdown indicator on QR badge - Regenerate QR button - QRLjacking detection toast with [Revoke All] action button (10s duration) - showToast enhanced with optional duration and action button support Fixes: - /api/logout now invalidates server-side session token (was only clearing browser cookie, leaving token valid for replay) Tests: 20 new tests in test/qr-auth.test.ts covering token lifecycle, bias check, rate limiting, SVG caching, and full server integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ import { writeHooksConfig, updateCaseEnvVars } from '../../hooks-config.js';
|
||||
import { generateClaudeMd } from '../../templates/claude-md.js';
|
||||
import { imageWatcher } from '../../image-watcher.js';
|
||||
import { getLifecycleLog } from '../../session-lifecycle-log.js';
|
||||
import type { SessionPort, EventPort, ConfigPort, InfraPort } from '../ports/index.js';
|
||||
import type { SessionPort, EventPort, ConfigPort, InfraPort, AuthPort } from '../ports/index.js';
|
||||
import { MAX_CONCURRENT_SESSIONS } from '../../config/map-limits.js';
|
||||
import { RunSummaryTracker } from '../../run-summary.js';
|
||||
|
||||
@@ -56,11 +56,16 @@ const LEADING_WHITESPACE_PATTERN = /^[\s\r\n]+/;
|
||||
|
||||
export function registerSessionRoutes(
|
||||
app: FastifyInstance,
|
||||
ctx: SessionPort & EventPort & ConfigPort & InfraPort
|
||||
ctx: SessionPort & EventPort & ConfigPort & InfraPort & AuthPort
|
||||
): void {
|
||||
// ========== Logout ==========
|
||||
|
||||
app.post('/api/logout', async (_req, reply) => {
|
||||
app.post('/api/logout', async (req, reply) => {
|
||||
// Invalidate server-side session token (not just the browser cookie)
|
||||
const sessionToken = req.cookies[AUTH_COOKIE_NAME];
|
||||
if (sessionToken) {
|
||||
ctx.authSessions?.delete(sessionToken);
|
||||
}
|
||||
reply.clearCookie(AUTH_COOKIE_NAME, { path: '/' });
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user