468 lines
19 KiB
JavaScript
468 lines
19 KiB
JavaScript
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
|
|
require('dotenv').config();
|
|
const { WebSocketServer } = require('ws');
|
|
const http = require('http');
|
|
const jwt = require('jsonwebtoken');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse } = require('@simplewebauthn/server');
|
|
|
|
const PORT = process.env.PORT || 4001;
|
|
const JWT_SECRET = process.env.JWT_SECRET;
|
|
if (!JWT_SECRET) { console.error('[3Z VIOLATION] JWT_SECRET env var MUST be set. Exiting.'); process.exit(1); }
|
|
const WEBAUTHN_RP_ID = process.env.WEBAUTHN_RP_ID || 'jumpa.id';
|
|
const WEBAUTHN_EXPECTED_ORIGIN = process.env.WEBAUTHN_EXPECTED_ORIGIN || 'https://jumpa.id';
|
|
const STATE_FILE = path.join(__dirname, 'iam_state.json');
|
|
|
|
// IAM Omni-Policy State
|
|
let iamState = {
|
|
identities: {
|
|
'AEGIS-SUPREME-974EFB44BE-X': {
|
|
role: 'supreme_admin',
|
|
tenant_id: 'SUPREME-MODE',
|
|
packages: ['phase-1-to-75', 'aegis-synthetica', 'ouroboros-protocol']
|
|
},
|
|
'AEGIS-TENANT-80AA3EEBE8-X': {
|
|
role: 'tenant',
|
|
tenant_id: 'JUMPA.ID ENTERPRISE',
|
|
packages: ['JUMPA.ID OMNI-ENGINE']
|
|
}
|
|
},
|
|
policies: {
|
|
'AEGIS-TENANT-80AA3EEBE8-X': {
|
|
name: 'JUMPA.ID ENTERPRISE',
|
|
mfa_mode: 'free', // 'free', 'strict', 'random'
|
|
supreme_allowed: {
|
|
biometric: true,
|
|
optical: true,
|
|
aegis: true
|
|
},
|
|
tenant_enabled: {
|
|
biometric: false,
|
|
optical: false,
|
|
aegis: true
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const Redis = require('ioredis');
|
|
|
|
// Connect to Redis for live sync
|
|
const redisUrl = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
|
|
const redis = new Redis(redisUrl);
|
|
redis.subscribe('AEGIS_IAM_STATE_CHANNEL', (err) => {
|
|
if (err) console.error('[IAM GATEKEEPER] Failed to subscribe to Redis:', err);
|
|
else console.log('[IAM GATEKEEPER] Subscribed to Redis channel AEGIS_IAM_STATE_CHANNEL');
|
|
});
|
|
|
|
redis.on('message', (channel, message) => {
|
|
if (channel === 'AEGIS_IAM_STATE_CHANNEL') {
|
|
try {
|
|
const newState = JSON.parse(message);
|
|
iamState = { ...iamState, ...newState };
|
|
console.log('[IAM GATEKEEPER] Synced state from Neural Relay (Redis)');
|
|
// Broadcast to connected WS clients
|
|
const broadcastData = JSON.stringify({ type: 'SYNC_IAM_POLICY', payload: iamState });
|
|
wss.clients.forEach(client => {
|
|
if (client.readyState === 1) client.send(broadcastData);
|
|
});
|
|
} catch (e) {
|
|
console.error('[IAM GATEKEEPER] Error parsing Redis message:', e);
|
|
}
|
|
}
|
|
});
|
|
|
|
const BILLING_MATRIX_URL = process.env.BILLING_MATRIX_URL || 'http://127.0.0.1:8082';
|
|
|
|
// Hydrate Policy from DuckDB / Billing Matrix
|
|
const hydrateState = async () => {
|
|
try {
|
|
const res = await fetch(`${BILLING_MATRIX_URL}/v1/iam/state`);
|
|
const loadedState = await res.json();
|
|
if (loadedState.identities && Object.keys(loadedState.identities).length > 0) {
|
|
iamState.identities = loadedState.identities;
|
|
iamState.policies = { ...iamState.policies, ...loadedState.policies };
|
|
console.log('[IAM GATEKEEPER] Successfully hydrated policies from DuckDB.');
|
|
} else {
|
|
console.log('[IAM GATEKEEPER] DuckDB state empty, using defaults. Seeding DB...');
|
|
saveStateToFile(); // Will actually post to DuckDB
|
|
}
|
|
} catch (err) {
|
|
console.error('[IAM GATEKEEPER] Failed to fetch state from DuckDB.', err.message);
|
|
}
|
|
};
|
|
hydrateState();
|
|
|
|
const saveStateToFile = () => {
|
|
fetch(`${BILLING_MATRIX_URL}/v1/iam/state`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(iamState)
|
|
}).catch(err => {
|
|
console.error('[IAM GATEKEEPER] Failed to save state to DuckDB.', err.message);
|
|
});
|
|
};
|
|
|
|
|
|
const server = http.createServer((req, res) => {
|
|
res.writeHead(200, { 'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': '*' });
|
|
res.end('XCU IAM Aegis Gatekeeper + Policy Engine + Key Forge is Online.\n');
|
|
});
|
|
|
|
const wss = new WebSocketServer({ server });
|
|
|
|
let activeAuthSessions = {}; // { sessionId: { targetKeyId, sequence: [], currentIndex: 0, ws: websocket } }
|
|
|
|
wss.on('connection', (ws) => {
|
|
console.log('[IAM GATEKEEPER] Quantum Client Connected.');
|
|
|
|
// Send the current policy state and mapped keys immediately
|
|
ws.send(JSON.stringify({
|
|
type: 'SYNC_IAM_POLICY',
|
|
payload: iamState
|
|
}));
|
|
|
|
const opticalInterval = setInterval(() => {
|
|
if (ws.readyState === 1) {
|
|
ws.send(JSON.stringify({
|
|
type: 'OPTICAL_CHALLENGE',
|
|
payload: `XCU-OPT-${Math.random().toString(36).substr(2, 9).toUpperCase()}`
|
|
}));
|
|
}
|
|
}, 5000);
|
|
|
|
ws.on('message', async (message) => {
|
|
try {
|
|
const data = JSON.parse(message);
|
|
|
|
// ==========================================
|
|
// 1. POLICY MUTATION HUB (DevSecOps)
|
|
// ==========================================
|
|
if (data.type === 'UPDATE_IAM_POLICY') {
|
|
const { keyId, supreme_allowed, tenant_enabled, mfa_mode } = data.payload;
|
|
if (iamState.policies[keyId]) {
|
|
if (supreme_allowed) iamState.policies[keyId].supreme_allowed = supreme_allowed;
|
|
if (tenant_enabled) iamState.policies[keyId].tenant_enabled = tenant_enabled;
|
|
if (mfa_mode) iamState.policies[keyId].mfa_mode = mfa_mode;
|
|
|
|
saveStateToFile();
|
|
|
|
const broadcastData = JSON.stringify({ type: 'SYNC_IAM_POLICY', payload: iamState });
|
|
wss.clients.forEach(client => {
|
|
if (client.readyState === 1) client.send(broadcastData);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. AEGIS KEY FORGE (Zero-Database Credential Swap)
|
|
// ==========================================
|
|
if (data.type === 'REGENERATE_AEGIS_KEY') {
|
|
const { oldKeyId } = data.payload;
|
|
const identity = iamState.identities[oldKeyId];
|
|
|
|
if (identity) {
|
|
// Create new chaotic string
|
|
const newKeyString = `AEGIS-${identity.role === 'supreme_admin' ? 'SUPREME' : 'TENANT'}-${Math.random().toString(36).substr(2, 10).toUpperCase()}-X`;
|
|
|
|
// Move identity to new Key
|
|
iamState.identities[newKeyString] = identity;
|
|
delete iamState.identities[oldKeyId];
|
|
|
|
// If it's a tenant, move the policy as well
|
|
if (iamState.policies[oldKeyId]) {
|
|
iamState.policies[newKeyString] = iamState.policies[oldKeyId];
|
|
delete iamState.policies[oldKeyId];
|
|
}
|
|
|
|
saveStateToFile();
|
|
console.log(`[IAM FORGE] Key Rotated: ${oldKeyId} -> ${newKeyString}`);
|
|
|
|
// Send the new key back exclusively to the requester so they can download it
|
|
ws.send(JSON.stringify({
|
|
type: 'NEW_KEYCARD_GENERATED',
|
|
payload: { oldKeyId, newKeyString, role: identity.role }
|
|
}));
|
|
|
|
// Broadcast state to all
|
|
const broadcastData = JSON.stringify({ type: 'SYNC_IAM_POLICY', payload: iamState });
|
|
wss.clients.forEach(client => {
|
|
if (client.readyState === 1) client.send(broadcastData);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 2.5 WEBAUTHN HARDWARE BINDING (FASE 39)
|
|
// ==========================================
|
|
if (data.type === 'GENERATE_REGISTRATION_OPTIONS') {
|
|
const { targetKeyId } = data.payload;
|
|
const identity = iamState.identities[targetKeyId];
|
|
if (identity) {
|
|
const user = {
|
|
id: Buffer.from(targetKeyId).toString('base64url'),
|
|
name: targetKeyId,
|
|
displayName: identity.tenant_id
|
|
};
|
|
|
|
const options = await generateRegistrationOptions({
|
|
rpName: 'XCU Ultra Gatekeeper',
|
|
rpID: WEBAUTHN_RP_ID,
|
|
userID: user.id,
|
|
userName: user.name,
|
|
attestationType: 'none',
|
|
authenticatorSelection: {
|
|
residentKey: 'required',
|
|
userVerification: 'preferred',
|
|
}
|
|
});
|
|
|
|
iamState.identities[targetKeyId].currentChallenge = options.challenge;
|
|
ws.send(JSON.stringify({ type: 'REGISTRATION_OPTIONS', payload: options }));
|
|
}
|
|
}
|
|
|
|
if (data.type === 'VERIFY_REGISTRATION_RESPONSE') {
|
|
const { targetKeyId, response } = data.payload;
|
|
const identity = iamState.identities[targetKeyId];
|
|
if (identity && identity.currentChallenge) {
|
|
try {
|
|
const verification = await verifyRegistrationResponse({
|
|
response: response,
|
|
expectedChallenge: identity.currentChallenge,
|
|
expectedOrigin: WEBAUTHN_EXPECTED_ORIGIN,
|
|
expectedRPID: WEBAUTHN_RP_ID,
|
|
});
|
|
|
|
if (verification.verified) {
|
|
const { registrationInfo } = verification;
|
|
iamState.identities[targetKeyId].webAuthn = {
|
|
credentialID: Buffer.from(registrationInfo.credentialID).toString('base64url'),
|
|
credentialPublicKey: Buffer.from(registrationInfo.credentialPublicKey).toString('base64url'),
|
|
counter: registrationInfo.counter,
|
|
};
|
|
delete iamState.identities[targetKeyId].currentChallenge;
|
|
saveStateToFile();
|
|
ws.send(JSON.stringify({ type: 'REGISTRATION_SUCCESS', payload: 'Hardware Biometrik Berhasil Didaftarkan' }));
|
|
}
|
|
} catch (e) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'WebAuthn Registration Failed: ' + e.message }));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.type === 'GENERATE_AUTHENTICATION_OPTIONS') {
|
|
const options = await generateAuthenticationOptions({
|
|
rpID: 'localhost',
|
|
userVerification: 'required',
|
|
});
|
|
// Save challenge globally for verification later (since we don't know who is logging in yet)
|
|
iamState.globalAuthChallenge = options.challenge;
|
|
ws.send(JSON.stringify({ type: 'AUTHENTICATION_OPTIONS', payload: options }));
|
|
}
|
|
|
|
// ==========================================
|
|
// 2.8 AIR-GAPPED MOBILE PAIRING (FASE 41)
|
|
// ==========================================
|
|
if (data.type === 'GENERATE_OPTICAL_PAIRING') {
|
|
const { targetKeyId } = data.payload;
|
|
if (iamState.identities[targetKeyId]) {
|
|
const secret = crypto.randomBytes(32).toString('hex');
|
|
iamState.identities[targetKeyId].pendingOpticalSecret = secret;
|
|
ws.send(JSON.stringify({
|
|
type: 'OPTICAL_PAIRING_QR',
|
|
payload: `PAIR|${targetKeyId}|${secret}`
|
|
}));
|
|
}
|
|
}
|
|
|
|
if (data.type === 'CONFIRM_OPTICAL_PAIRING') {
|
|
const { targetKeyId, secret } = data.payload;
|
|
const identity = iamState.identities[targetKeyId];
|
|
if (identity && identity.pendingOpticalSecret === secret) {
|
|
identity.opticalSecret = secret;
|
|
delete identity.pendingOpticalSecret;
|
|
saveStateToFile();
|
|
ws.send(JSON.stringify({ type: 'OPTICAL_PAIRING_SUCCESS', payload: 'Perangkat Kuantum Berhasil Dikawinkan.' }));
|
|
} else {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Gagal mengawinkan perangkat.' }));
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 3. AEGIS AUTHENTICATION HUB (MFA ORCHESTRATOR)
|
|
// ==========================================
|
|
if (data.type === 'AEGIS_AUTH_REQUEST') {
|
|
const { authType, keyPayload, sessionId } = data.payload;
|
|
console.log(`[IAM GATEKEEPER] Auth Request: ${authType}`);
|
|
|
|
let targetKeyId = null;
|
|
|
|
if (authType === 'BIOMETRIC_PULSE') {
|
|
const response = keyPayload; // keyPayload is the credential response from browser
|
|
// Find which identity this credential belongs to
|
|
targetKeyId = Object.keys(iamState.identities).find(k =>
|
|
iamState.identities[k].webAuthn &&
|
|
iamState.identities[k].webAuthn.credentialID === response.id
|
|
);
|
|
|
|
if (targetKeyId && iamState.identities[targetKeyId].webAuthn) {
|
|
try {
|
|
const verification = await verifyAuthenticationResponse({
|
|
response: response,
|
|
expectedChallenge: iamState.globalAuthChallenge,
|
|
expectedOrigin: WEBAUTHN_EXPECTED_ORIGIN,
|
|
expectedRPID: WEBAUTHN_RP_ID,
|
|
authenticator: {
|
|
credentialID: Buffer.from(iamState.identities[targetKeyId].webAuthn.credentialID, 'base64url'),
|
|
credentialPublicKey: Buffer.from(iamState.identities[targetKeyId].webAuthn.credentialPublicKey, 'base64url'),
|
|
counter: iamState.identities[targetKeyId].webAuthn.counter,
|
|
}
|
|
});
|
|
|
|
if (verification.verified) {
|
|
const { authenticationInfo } = verification;
|
|
iamState.identities[targetKeyId].webAuthn.counter = authenticationInfo.newCounter;
|
|
saveStateToFile();
|
|
} else {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Tanda Tangan Biometrik Tidak Valid.' }));
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Gagal Memverifikasi Biometrik: ' + e.message }));
|
|
return;
|
|
}
|
|
} else {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Hardware Biometrik Belum Terdaftar.' }));
|
|
return;
|
|
}
|
|
} else if (authType === 'AEGIS_KEYCARD') {
|
|
targetKeyId = keyPayload;
|
|
} else if (authType === 'OPTICAL_SYNC') {
|
|
// keyPayload carries { targetKeyId, signature, challenge }
|
|
targetKeyId = keyPayload.targetKeyId;
|
|
const identity = iamState.identities[targetKeyId];
|
|
|
|
if (!identity || !identity.opticalSecret) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Ponsel ini belum di-Pairing (Dikawinkan) di dalam Dasbor.' }));
|
|
return;
|
|
}
|
|
|
|
// Verify HMAC-SHA256 signature
|
|
const expectedSignature = crypto.createHmac('sha256', identity.opticalSecret)
|
|
.update(keyPayload.challenge)
|
|
.digest('hex');
|
|
|
|
if (keyPayload.signature !== expectedSignature) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Tanda Tangan Kriptografi Ponsel DITOLAK!' }));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (targetKeyId && iamState.identities[targetKeyId]) {
|
|
const identity = iamState.identities[targetKeyId];
|
|
const policy = iamState.policies[targetKeyId];
|
|
|
|
const typeMap = { 'BIOMETRIC_PULSE': 'biometric', 'OPTICAL_SYNC': 'optical', 'AEGIS_KEYCARD': 'aegis' };
|
|
const methodKey = typeMap[authType];
|
|
|
|
// === STRICT POLICY ENFORCEMENT CHECK ===
|
|
if (identity.role === 'tenant' && policy) {
|
|
if (!policy.supreme_allowed[methodKey]) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: `ACCESS DENIED: Protokol [${methodKey}] telah dicabut secara absolut oleh Supreme Admin.` }));
|
|
return;
|
|
}
|
|
if (!policy.tenant_enabled[methodKey]) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: `ACCESS DENIED: Anda telah menonaktifkan protokol [${methodKey}] di Security Enclave Anda.` }));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// === MFA ORCHESTRATION ===
|
|
if (identity.role === 'tenant' && policy) {
|
|
const mode = policy.mfa_mode || 'free';
|
|
const activeLayers = ['biometric', 'optical', 'aegis'].filter(k => policy.supreme_allowed[k] && policy.tenant_enabled[k]);
|
|
|
|
if (mode !== 'free' && activeLayers.length > 1) {
|
|
// Multi-Factor required
|
|
if (!sessionId) {
|
|
// Initialize new session
|
|
const newSessionId = `MFA-SESS-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
|
let sequence = [...activeLayers];
|
|
if (mode === 'random') {
|
|
sequence = sequence.sort(() => Math.random() - 0.5);
|
|
}
|
|
|
|
// If the first step in sequence is what they just submitted, advance. Otherwise, reject and tell them the sequence
|
|
if (sequence[0] === methodKey) {
|
|
activeAuthSessions[newSessionId] = { targetKeyId, sequence, currentIndex: 1, ws };
|
|
ws.send(JSON.stringify({
|
|
type: 'MFA_STEP_REQUIRED',
|
|
payload: { sessionId: newSessionId, nextStep: sequence[1], sequence, currentIndex: 1 }
|
|
}));
|
|
return;
|
|
} else {
|
|
ws.send(JSON.stringify({
|
|
type: 'MFA_SEQUENCE_STARTED',
|
|
payload: { sessionId: newSessionId, sequence, currentIndex: 0, message: `Kebijakan Multi-Lapis Aktif. Anda wajib mematuhi urutan: ${sequence.join(' -> ').toUpperCase()}` }
|
|
}));
|
|
return;
|
|
}
|
|
} else {
|
|
// Advance existing session
|
|
const session = activeAuthSessions[sessionId];
|
|
if (!session || session.targetKeyId !== targetKeyId) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Sesi MFA tidak valid atau telah usang.' }));
|
|
return;
|
|
}
|
|
if (session.sequence[session.currentIndex] !== methodKey) {
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: `Pelanggaran Urutan! Langkah yang diharapkan: ${session.sequence[session.currentIndex].toUpperCase()}` }));
|
|
delete activeAuthSessions[sessionId];
|
|
return;
|
|
}
|
|
session.currentIndex++;
|
|
if (session.currentIndex < session.sequence.length) {
|
|
ws.send(JSON.stringify({
|
|
type: 'MFA_STEP_REQUIRED',
|
|
payload: { sessionId, nextStep: session.sequence[session.currentIndex], sequence: session.sequence, currentIndex: session.currentIndex }
|
|
}));
|
|
return;
|
|
}
|
|
// If finished, delete session and grant access
|
|
delete activeAuthSessions[sessionId];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Issue JWT if single-layer, free mode, or MFA completed
|
|
console.log('[IAM GATEKEEPER] Auth Successful. Issuing JWT.');
|
|
const token = jwt.sign({
|
|
role: identity.role,
|
|
tenant_id: identity.tenant_id,
|
|
used_gb: Math.random() * 50,
|
|
quota_limit_gb: 500,
|
|
packages: identity.packages
|
|
}, JWT_SECRET, { expiresIn: '24h' });
|
|
|
|
ws.send(JSON.stringify({ type: 'AUTH_SUCCESS', payload: { token } }));
|
|
} else {
|
|
console.warn('[IAM GATEKEEPER] Auth Failed. Invalid Key.');
|
|
ws.send(JSON.stringify({ type: 'AUTH_FAILED', payload: 'Kriptografi Gagal Divalidasi. Kunci Fisik ini mungkin sudah Hangus (Revoked).' }));
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('[IAM GATEKEEPER] Payload parsing error:', err);
|
|
}
|
|
});
|
|
|
|
ws.on('close', () => {
|
|
clearInterval(opticalInterval);
|
|
});
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`[IAM GATEKEEPER] Listening on ws://localhost:${PORT}`);
|
|
});
|