158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
// [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 });
|
|
}
|
|
}
|