import { getApprovalByEmailToken, approveRequest } from "../../../../db/device-approvals"; /** * POST /api/devices/approvals/email/:token * * Email-Magic-Link-Endpoint. KEIN Auth nötig — der token (32-char hex) ist das * Secret. Wird vom App-Web-Frontend (/approve-device?token=...) aufgerufen. * * Es wird KEIN evictDeviceRowId-Parameter unterstützt — wenn das Limit voll * ist, evictiert der Server das ältest-genutzte Device (lastSeenAt ASC). * Das ist der Trade-Off für Email-Fallback ohne Auth. */ export default defineEventHandler(async (event) => { const token = getRouterParam(event, "token"); if (!token || token.length !== 64) { throw createError({ statusCode: 400, message: "invalid token" }); } const approval = await getApprovalByEmailToken(token); if (!approval) { throw createError({ statusCode: 404, message: "approval not found" }); } if (approval.status !== "pending") { throw createError({ statusCode: 409, message: `approval is ${approval.status}`, data: { approval }, }); } // Auto-evict: wenn User am Limit ist, ältest-gesehenes UNGEBUNDENES Device // entfernen. Gebundene Pro/Legend-Devices werden NIE auto-evictiert (die // brauchen den 24h-Release-Flow). Sicherheit: der token ist das Secret. const { usePrisma } = await import("../../../../utils/prisma"); const { getProfile } = await import("../../../../db/profile"); const { getPlanLimits } = await import("../../../../utils/plan-features"); const db = usePrisma(); const profile = await getProfile(approval.userId); const limits = getPlanLimits(profile?.plan ?? "free"); const userDevices = await db.userDevice.findMany({ where: { userId: approval.userId }, orderBy: { lastSeenAt: "asc" }, select: { id: true, boundToPlan: true, deviceId: true }, }); // Schon registriert? (Race wenn User mehrfach klickt) → kein evict nötig. const alreadyRegistered = userDevices.some( (d) => d.deviceId === approval.newDeviceId, ); let evictId: string | null = null; if (!alreadyRegistered && userDevices.length >= limits.maxAppDevices) { const oldestUnbound = userDevices.find((d) => !d.boundToPlan); if (!oldestUnbound) { throw createError({ statusCode: 409, message: "all_devices_locked", data: { error: "Alle Geräte sind plan-gebunden — bitte erst freigeben" }, }); } evictId = oldestUnbound.id; } const result = await approveRequest({ approvalId: approval.id, userId: approval.userId, approvedByDeviceRowId: null, evictDeviceRowId: evictId, }); if (!result) { throw createError({ statusCode: 500, message: "failed to approve" }); } return { approval: result }; });