Letschat / src /app /api /codes /validate /route.ts
HonzaH's picture
Upload 171 files
9853b20 verified
Raw
History Blame Contribute Delete
4.14 kB
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 }
)
}
}