import { error } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/db'; import { clientIp, jsonOk, rateLimited } from '$lib/server/v2-helpers'; import { rateLimit } from '$lib/server/v2-rate-limit'; import { listBlocks } from '$lib/server/schedule'; import { serializeScheduleBlock } from '$lib/server/v2-serializers'; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; /** * Machine-to-app endpoint used by the RadiantWave kiosk. Authenticates with * `email` + `licenseKey` query parameters and validates only that the pair * belong together — license active/entitlement is deliberately not checked * here, since an invalid license would already lock the kiosk out. */ export const GET: RequestHandler = async ({ request, url }) => { const ip = clientIp(request); const rl = rateLimit({ key: `schedule:${ip}`, limit: 10, windowMs: 60_000 }); if (!rl.ok) { return rateLimited(rl.retryAfterMs); } const email = (url.searchParams.get('email') ?? '').trim().toLowerCase(); const licenseKey = ( url.searchParams.get('licenseKey') ?? url.searchParams.get('key') ?? '' ).trim(); if (!email || !licenseKey || email.length > 254 || !EMAIL_RE.test(email)) { throw error(400, 'not-authorized'); } const storedKey = await prisma.licenseKey.findUnique({ where: { key: licenseKey } }); if (!storedKey) { throw error(400, 'not-authorized'); } const user = await prisma.user.findUnique({ where: { email } }); if (!user) { throw error(400, 'not-authorized'); } // Licenses created before accounts were linked may carry only an email. const belongs = storedKey.userId === user.id || (storedKey.userId === null && storedKey.email?.toLowerCase() === email); if (!belongs) { throw error(400, "this key doesn't appear to belong to you"); } const blocks = await listBlocks(user.id); const serialized = blocks.map((block) => serializeScheduleBlock( block, block.proclamations.map((entry) => entry.proclamation.id) ) ); return jsonOk({ email, blocks: serialized }); };