diff --git a/__tests__/actions/pairing.test.ts b/__tests__/actions/pairing.test.ts index 47b1db0..da8d92f 100644 --- a/__tests__/actions/pairing.test.ts +++ b/__tests__/actions/pairing.test.ts @@ -159,13 +159,12 @@ describe('actions/pairing', () => { expect(arg.data.status).toBe('cancelled') }) - it('demo-user mag annuleren', async () => { + it('demo-user wordt geblokkeerd, geen DB-write', async () => { mockGetSession.mockResolvedValue(SESSION_DEMO) - mockPrisma.loginPairing.findUnique.mockResolvedValue(pendingPairing()) - mockPrisma.loginPairing.update.mockResolvedValue({}) - const res = await cancelPairing(VALID_PAIRING_ID, VALID_SECRET) - expect(res).toEqual({ ok: true }) + expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' }) + expect(mockPrisma.loginPairing.findUnique).not.toHaveBeenCalled() + expect(mockPrisma.loginPairing.update).not.toHaveBeenCalled() }) }) }) diff --git a/actions/pairing.ts b/actions/pairing.ts index 6f25c8a..6fedd3f 100644 --- a/actions/pairing.ts +++ b/actions/pairing.ts @@ -126,6 +126,8 @@ export async function cancelPairing( ): Promise<{ ok: true } | ActionFail> { const session = await getSession() if (!session.userId) return { ok: false, error: 'Niet ingelogd' } + // Cancel is een DB-write — onder de demo-write-block-regel. + if (session.isDemo) return { ok: false, error: 'Niet beschikbaar in demo-modus' } const parsed = inputSchema.safeParse({ pairingId, mobileSecret }) if (!parsed.success) return { ok: false, error: 'Ongeldige invoer' } diff --git a/app/api/auth/pair/stream/[pairingId]/route.ts b/app/api/auth/pair/stream/[pairingId]/route.ts index 9ad5b53..049044d 100644 --- a/app/api/auth/pair/stream/[pairingId]/route.ts +++ b/app/api/auth/pair/stream/[pairingId]/route.ts @@ -152,17 +152,27 @@ export async function GET( cleanup('pg error') }) - // Initial state — voorkomt race waarbij approve net vóór SSE-open valt + // Initial state — dicht de race tussen pair/start en SSE-open. De + // *eerste* findUnique (voor cookie-validatie) gebeurde vóór LISTEN + // actief was; als de mobiel tussen die query en LISTEN approvet is + // de pg_notify verloren (Postgres queuet niet) én is de eerder + // gelezen status stale. Lees daarom de status hier opnieuw — nu LISTEN + // wel actief is, dus alle approvals na dit punt komen via de notify- + // handler door. + const fresh = await prisma.loginPairing.findUnique({ + where: { id: pairingId }, + select: { status: true }, + }) + const currentStatus = fresh?.status ?? pairing.status + enqueue( `event: state\ndata: ${JSON.stringify({ pairing_id: pairingId, - status: pairing.status, + status: currentStatus, })}\n\n`, ) - // Pairing was misschien al consumed/cancelled tussen findUnique en LISTEN — - // sluit de stream meteen. - if (TERMINAL_STATUSES.has(pairing.status)) { - await cleanup(`already-${pairing.status}`) + if (TERMINAL_STATUSES.has(currentStatus)) { + await cleanup(`already-${currentStatus}`) return }