[TSM.ID].[11031972] PXE : Platform X Ecosystem I [118 Module -LIVE-]

This commit is contained in:
TSM.ID
2026-05-25 03:50:05 +07:00
commit e820143b3c
673 changed files with 101320 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
// [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 });
}
}