import { NextRequest, NextResponse } from 'next/server' import { codeEntrySchema } from 'lib/validations/code' import { getAnonymousSession, setAnonymousSession } from 'lib/session' import { generateSessionHash } from 'lib/codes' import { getCodeByCode, updateCode } from 'lib/persistence' import { assertNotBlocked, noteFailure, resetFailures } from 'lib/rateLimiter' import { logEvent } from 'lib/logger' export async function POST(request: NextRequest) { try { const body = await request.json() const validation = codeEntrySchema.safeParse(body) if (!validation.success) { return NextResponse.json( { error: 'Neplatnì form t k¢du' }, { status: 400 } ) } const { code } = validation.data const headers = (request as any).headers const ip = headers?.get?.('x-forwarded-for') || 'unknown' const ua = headers?.get?.('user-agent') || 'unknown' const deviceKey = `${ip}:${ua}` try { assertNotBlocked(ip) assertNotBlocked(deviceKey) } catch (_e) { return NextResponse.json({ error: 'Pý¡liç mnoho pokus… – zkuste to pozdØji' }, { status: 429 }) } // Check if code exists via persistence (local-first) const codeData = await getCodeByCode(code) if (!codeData) { noteFailure(ip, { limit: 3, windowMs: 5 * 60 * 1000, blockMs: 5 * 60 * 1000, escalateAfterBlocks: 3, escalateBlockMs: 24 * 60 * 60 * 1000 }) noteFailure(deviceKey, { limit: 3, windowMs: 5 * 60 * 1000, blockMs: 5 * 60 * 1000, escalateAfterBlocks: 3, escalateBlockMs: 24 * 60 * 60 * 1000 }) return NextResponse.json( { error: 'K¢d nenalezen' }, { status: 404 } ) } // Code is not used yet - first time if (codeData.used !== 1) { await logEvent({ module: 'codes', operation: 'validate_unused', data: { codeId: codeData.id }, ip, userAgent: ua }) return NextResponse.json({ redirect: `/setup?code=${code.toUpperCase()}`, needsSetup: true }) } // Code is used - check access method if (codeData.pin_hash) { // Protected by PIN const session = await getAnonymousSession() if (session && session.codeId === codeData.id) { // User has valid session return NextResponse.json({ redirect: `/room/${codeData.room_id}?code=${code.toUpperCase()}`, roomId: codeData.room_id }) } else { // Needs PIN return NextResponse.json({ redirect: `/enter-pin?code=${code.toUpperCase()}`, needsPin: true }) } } else { // No PIN protection - ensure session exists and room is set if (!codeData.room_id) { return NextResponse.json( { error: 'M¡stnost pro tento k¢d nen¡ dostupn ' }, { status: 500 } ) } const session = await getAnonymousSession() const hasValidSession = session && session.codeId === codeData.id && session.sessionHash === codeData.session_hash if (!hasValidSession) { const userAgent = request.headers.get('user-agent') || '' const sessionHash = codeData.session_hash || generateSessionHash(code, userAgent) await setAnonymousSession({ codeId: codeData.id, sessionHash, roomId: codeData.room_id, expiresAt: Date.now() + (10 * 365 * 24 * 60 * 60 * 1000) // 10 let stejne jako flow bez PIN }) await updateCode(codeData.id, { session_hash: sessionHash }) } await logEvent({ module: 'codes', operation: 'validate_success', data: { codeId: codeData.id, roomId: codeData.room_id }, ip, userAgent: ua }) resetFailures(ip) resetFailures(deviceKey) return NextResponse.json({ redirect: `/room/${codeData.room_id}?code=${code.toUpperCase()}`, roomId: codeData.room_id }) } } catch (error) { console.error('Code validation error:', error) return NextResponse.json( { error: 'Vnitýn¡ chyba serveru' }, { status: 500 } ) } }