// [TSM.ID].[11031972] — All Rights Reserved. Proprietary & Confidential. import { NextResponse } from 'next/server'; import { db } from "@/drizzle/db"; import { tenants, guestInvites } from "@/drizzle/schema"; import { eq, and } from 'drizzle-orm'; import jwt from 'jsonwebtoken'; export const dynamic = 'force-dynamic'; /** * GUEST TOKEN ENDPOINT — Zoom-Like Guest Access * * Alur: * 1. Guest klik link room → VC tampilkan Lobby UI * 2. Guest isi nama → POST ke endpoint ini * 3. IAM verifikasi room ada + tenant mengizinkan guest * 4. Issue JWT terbatas (role: "guest", durasi diatur admin tenant) * 5. Guest masuk room dengan permissions terbatas */ export async function POST(req: Request) { try { const { roomName, displayName } = await req.json(); if (!roomName || !displayName || displayName.trim().length < 2) { return NextResponse.json({ error: 'Nama tampilan wajib diisi (minimal 2 karakter).' }, { status: 400 }); } // Sanitize display name const safeName = displayName.trim().substring(0, 50); // Extract tenantId from room name format: "tenantId-roomSlug" or find by room // For now, we look up rooms in guestInvites to find the host tenant // Or extract from room name pattern // Strategy: Look at all tenants and check if guest access is enabled // The room owner's tenant determines the guest policy const allTenants = await db.select().from(tenants).limit(50); // Find the tenant that owns this room // Room format convention: rooms typically include tenant context // For universal guest access, we find tenant with guest enabled let ownerTenant = null; for (const t of allTenants) { try { const lic = typeof t.licenses === 'string' ? JSON.parse(t.licenses) : (t.licenses || {}); // Check if tenant has guest access enabled (set by admin) if (lic['jvc.guest_access'] === 'GRANTED' || lic['jvc.guest_access'] === true) { // Check if room belongs to this tenant (room contains tenant name or id) if (roomName.toLowerCase().includes(t.name.toLowerCase().replace(/\s+/g, '-')) || roomName.includes(t.id.substring(0, 8))) { ownerTenant = t; break; } } } catch (_) { /* skip malformed licenses */ } } // Fallback: if no tenant found by name match, check if ANY tenant allows guest if (!ownerTenant) { for (const t of allTenants) { try { const lic = typeof t.licenses === 'string' ? JSON.parse(t.licenses) : (t.licenses || {}); if (lic['jvc.guest_access'] === 'GRANTED' || lic['jvc.guest_access'] === true) { ownerTenant = t; break; } } catch (_) {} } } if (!ownerTenant) { return NextResponse.json({ error: 'Room ini tidak mengizinkan akses tamu. Harap minta host untuk mengaktifkan Guest Access.' }, { status: 403 }); } // Parse guest session duration from tenant licenses (admin configurable) let guestDurationHours = 2; // Default 2 jam try { const lic = typeof ownerTenant.licenses === 'string' ? JSON.parse(ownerTenant.licenses) : (ownerTenant.licenses || {}); if (lic['jvc.guest_duration_hours'] && !isNaN(Number(lic['jvc.guest_duration_hours']))) { guestDurationHours = Math.min(Math.max(Number(lic['jvc.guest_duration_hours']), 0.5), 24); // 30min - 24hr } } catch (_) {} // Generate guest JWT — limited permissions const xcuSecret = process.env.XCU_QCG_SECRET || process.env.XCU_TOKEN_SECRET; if (!xcuSecret) { throw new Error('XCU_QCG_SECRET tidak dikonfigurasi.'); } const guestToken = jwt.sign({ sub: `guest_${safeName.replace(/\s+/g, '_').toLowerCase()}`, email: `guest_${Date.now()}@guest.jumpa.id`, // Pseudo email for identity role: 'guest', tenantId: ownerTenant.id, tenantName: ownerTenant.name, displayName: safeName, allowCrossGroup: false, modules: [], // Guest tidak dapat modul apapun capabilities: { video: { features: { autopilot: true } }, audio: { features: {} }, ui: { 'jvc.ui.host_controls': false, 'jvc.ui.recording': false, 'jvc.ui.screen_share': false, // Admin bisa override via licenses 'jvc.ui.chat': true, 'jvc.ui.reactions': true, } }, isGuest: true, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (60 * 60 * guestDurationHours) }, xcuSecret); // Generate JUMPA.ID session cookie for guest (so quantum_token works too) const jumpaGuestToken = jwt.sign({ userId: `guest_${Date.now()}`, email: `guest_${Date.now()}@guest.jumpa.id`, role: 'guest', tenantId: ownerTenant.id, tenantName: ownerTenant.name, displayName: safeName, }, process.env.JWT_SECRET as string, { expiresIn: `${guestDurationHours}h` }); const response = NextResponse.json({ success: true, token: guestToken, displayName: safeName, tenantName: ownerTenant.name, guestDurationHours, message: `Selamat datang, ${safeName}! Sesi tamu berlaku ${guestDurationHours} jam.` }); // Set guest cookie so subsequent requests (quantum_token etc) work response.cookies.set({ name: 'jumpa_token', value: jumpaGuestToken, httpOnly: true, secure: true, sameSite: 'lax', path: '/', domain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN || undefined, maxAge: 60 * 60 * guestDurationHours, }); return response; } catch (error: unknown) { console.error('[GUEST TOKEN ERROR]', error); return NextResponse.json({ error: 'Gagal membuat sesi tamu.' }, { status: 500 }); } }