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 { getScheduleForLicense, scheduleBlocksToInput } from '$lib/server/schedule'; import { buildSchedulePayload } from '$lib/server/v2-serializers'; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; function scheduleType(value: string | null | undefined) { return value === 'daily' || value === 'weekly' ? value : null; } /** * 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. * * Extended additively with `version`, `type`, `daily`, and `weekly`. `email` * and `blocks` keep their exact previous meaning (daily flattening; empty for * weekly), so old Kiosks are unaffected. No ETag/304 here. */ 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 schedule = await getScheduleForLicense(storedKey.id); const type = scheduleType(schedule?.type); const blocks = schedule ? scheduleBlocksToInput(schedule.blocks) : []; const payload = buildSchedulePayload(type, blocks); return jsonOk({ email, ...payload }); };