File size: 4,136 Bytes
9853b20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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 }
    )
  }
}