Files

76 lines
2.4 KiB
TypeScript

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const LOCALES = ['id', 'en'];
// =====================================================================
// PANOPTICON LIVE KILL ENFORCEMENT (In-Memory LRU Cache)
// Pengguna yang sudah di-kill akan langsung ditolak di middleware layer
// tanpa menyentuh database (zero-latency 403).
// =====================================================================
const killCache = new Set<string>();
let lastKillSync = 0;
const KILL_SYNC_INTERVAL_MS = 10_000; // Sinkronisasi tiap 10 detik
async function syncKillList() {
const now = Date.now();
if (now - lastKillSync < KILL_SYNC_INTERVAL_MS) return;
lastKillSync = now;
try {
// Fetch kill list from internal API (server-side only)
const baseUrl = `http://127.0.0.1:${process.env.PORT || 3005}`;
const resp = await fetch(`${baseUrl}/api/telemetry/kill-list`, {
headers: { 'x-internal-secret': process.env.JWT_SECRET || '' },
signal: AbortSignal.timeout(3000),
});
if (resp.ok) {
const data = await resp.json();
killCache.clear();
if (Array.isArray(data.targets)) {
data.targets.forEach((id: string) => killCache.add(id));
}
}
} catch (_e) {
// Gagal sinkronisasi = tetap gunakan cache lama. Zero-Downtime.
}
}
function extractUserFromJWT(token: string): string | null {
try {
// Decode payload tanpa verifikasi (hanya untuk baca email di middleware)
const parts = token.split('.');
if (parts.length !== 3) return null;
const payload = JSON.parse(atob(parts[1]));
return payload.email || payload.userId || null;
} catch (_e) {
return null;
}
}
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// ---- PANOPTICON KILL CHECK ----
await syncKillList();
const token = request.cookies.get('jumpa_token')?.value;
if (token && killCache.size > 0) {
const userIdentity = extractUserFromJWT(token);
if (userIdentity && killCache.has(userIdentity)) {
return new NextResponse(
JSON.stringify({ error: 'Akses Dihanguskan oleh Otoritas Puncak. Sesi Anda telah dimusnahkan.' }),
{ status: 403, headers: { 'Content-Type': 'application/json' } }
);
}
}
// ---- NO MORE LOCALE REWRITE ----
return NextResponse.next();
}
export default middleware;
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)', '/']
};