lucifer-gate 0.3.1 → 0.4.0-alpha.2.a4a1d3e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/create_app.js +29 -12
- package/dist/server/create_app.js.map +1 -1
- package/dist/server/domains/command-gateway/api/register_approval_routes.js +203 -0
- package/dist/server/domains/command-gateway/api/register_approval_routes.js.map +1 -0
- package/dist/server/domains/command-gateway/service/multi_approval_channel.js +33 -0
- package/dist/server/domains/command-gateway/service/multi_approval_channel.js.map +1 -0
- package/dist/server/domains/command-gateway/service/request_telegram_approval.js +26 -19
- package/dist/server/domains/command-gateway/service/request_telegram_approval.js.map +1 -1
- package/dist/server/domains/command-gateway/service/web_approval_channel.js +77 -0
- package/dist/server/domains/command-gateway/service/web_approval_channel.js.map +1 -0
- package/package.json +2 -1
|
@@ -5,7 +5,7 @@ import { getServerConfig } from './domains/platform-api/config/server_config.js'
|
|
|
5
5
|
import { registerHealthRoutes } from './domains/platform-api/api/register_health_routes.js';
|
|
6
6
|
import { createRuntimeMetadataRepository } from './domains/platform-api/repository/runtime_metadata_repository.js';
|
|
7
7
|
import { createHealthReportService } from './domains/platform-api/service/create_health_report.js';
|
|
8
|
-
import { loadGatewayConfig,
|
|
8
|
+
import { loadGatewayConfig, getAdminSecret } from './domains/command-gateway/config/gateway_config.js';
|
|
9
9
|
import { getDatabase, closeDatabase } from './domains/command-gateway/repository/database.js';
|
|
10
10
|
import { createApprovalStore } from './domains/command-gateway/repository/approval_store.js';
|
|
11
11
|
import { createAuditLog } from './domains/command-gateway/repository/audit_log.js';
|
|
@@ -15,8 +15,35 @@ import { createPendingRequestStore } from './domains/command-gateway/repository/
|
|
|
15
15
|
import { registerExecuteRoutes } from './domains/command-gateway/api/register_execute_routes.js';
|
|
16
16
|
import { createTelegramApprovalChannel } from './domains/command-gateway/service/request_telegram_approval.js';
|
|
17
17
|
import { createAutoApproveChannel } from './domains/command-gateway/service/auto_approve_channel.js';
|
|
18
|
+
import { createWebApprovalChannel } from './domains/command-gateway/service/web_approval_channel.js';
|
|
19
|
+
import { createMultiApprovalChannel } from './domains/command-gateway/service/multi_approval_channel.js';
|
|
20
|
+
import { registerApprovalRoutes } from './domains/command-gateway/api/register_approval_routes.js';
|
|
18
21
|
import { createChildLogger } from './lib/logger.js';
|
|
19
22
|
const log = createChildLogger('app');
|
|
23
|
+
function initApprovalChannel(deps, autoApprove) {
|
|
24
|
+
if (autoApprove) {
|
|
25
|
+
return createAutoApproveChannel();
|
|
26
|
+
}
|
|
27
|
+
const channels = [];
|
|
28
|
+
const { app, pendingStore, approvalStore, auditLog, gatewayConfig } = deps;
|
|
29
|
+
const telegramToken = process.env.LUCIFER_TELEGRAM_TOKEN;
|
|
30
|
+
const chatId = gatewayConfig.telegramChatId ?? process.env.LUCIFER_TELEGRAM_CHAT_ID;
|
|
31
|
+
if (telegramToken && chatId) {
|
|
32
|
+
channels.push(createTelegramApprovalChannel(telegramToken, chatId, pendingStore, approvalStore, auditLog));
|
|
33
|
+
}
|
|
34
|
+
const adminSecret = getAdminSecret();
|
|
35
|
+
if (adminSecret) {
|
|
36
|
+
const webChannel = createWebApprovalChannel();
|
|
37
|
+
channels.push(webChannel);
|
|
38
|
+
registerApprovalRoutes({ router: app, adminSecret, webChannel, approvalStore, auditLog });
|
|
39
|
+
log.info('Web approval UI enabled at /admin/approvals');
|
|
40
|
+
}
|
|
41
|
+
if (channels.length === 0) {
|
|
42
|
+
throw new Error('No approval channels configured. Set LUCIFER_TELEGRAM_TOKEN + LUCIFER_TELEGRAM_CHAT_ID for Telegram, ' +
|
|
43
|
+
'or LUCIFER_ADMIN_SECRET for web UI, or use --auto-approve for development.');
|
|
44
|
+
}
|
|
45
|
+
return channels.length === 1 ? channels[0] : createMultiApprovalChannel(channels);
|
|
46
|
+
}
|
|
20
47
|
export function createApp(options = {}) {
|
|
21
48
|
const serverConfig = getServerConfig();
|
|
22
49
|
const metadataRepository = createRuntimeMetadataRepository();
|
|
@@ -46,17 +73,7 @@ export function createApp(options = {}) {
|
|
|
46
73
|
const apiKeyStore = createApiKeyStore(apiKeysPath);
|
|
47
74
|
const commandRulesStore = createCommandRulesStore(commandRulesPath);
|
|
48
75
|
const pendingStore = createPendingRequestStore();
|
|
49
|
-
|
|
50
|
-
approvalChannel = createAutoApproveChannel();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
const token = getTelegramToken();
|
|
54
|
-
const chatId = gatewayConfig.telegramChatId ?? process.env.LUCIFER_TELEGRAM_CHAT_ID;
|
|
55
|
-
if (!chatId) {
|
|
56
|
-
throw new Error('Telegram chat ID is required. Set LUCIFER_TELEGRAM_CHAT_ID env var or telegramChatId in config.');
|
|
57
|
-
}
|
|
58
|
-
approvalChannel = createTelegramApprovalChannel(token, chatId, pendingStore, approvalStore, auditLog);
|
|
59
|
-
}
|
|
76
|
+
approvalChannel = initApprovalChannel({ app, pendingStore, approvalStore, auditLog, gatewayConfig }, options.autoApprove ?? false);
|
|
60
77
|
registerExecuteRoutes({
|
|
61
78
|
router: app, config: gatewayConfig, apiKeyStore, commandRulesStore,
|
|
62
79
|
approvalStore, pendingStore, auditLog, approvalChannel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create_app.js","sourceRoot":"","sources":["../../server/src/create_app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAA;AAC3F,OAAO,EAAE,+BAA+B,EAAE,MAAM,kEAAkE,CAAA;AAClH,OAAO,EAAE,yBAAyB,EAAE,MAAM,wDAAwD,CAAA;AAClG,OAAO,EAAE,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"create_app.js","sourceRoot":"","sources":["../../server/src/create_app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sDAAsD,CAAA;AAC3F,OAAO,EAAE,+BAA+B,EAAE,MAAM,kEAAkE,CAAA;AAClH,OAAO,EAAE,yBAAyB,EAAE,MAAM,wDAAwD,CAAA;AAClG,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAA;AACtG,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAA;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,wDAAwD,CAAA;AAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,mDAAmD,CAAA;AAClF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uDAAuD,CAAA;AACzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,6DAA6D,CAAA;AACrG,OAAO,EAAE,yBAAyB,EAAE,MAAM,+DAA+D,CAAA;AACzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,0DAA0D,CAAA;AAChG,OAAO,EAAE,6BAA6B,EAAE,MAAM,gEAAgE,CAAA;AAC9G,OAAO,EAAE,wBAAwB,EAAE,MAAM,2DAA2D,CAAA;AACpG,OAAO,EAAE,wBAAwB,EAAE,MAAM,2DAA2D,CAAA;AACpG,OAAO,EAAE,0BAA0B,EAAE,MAAM,6DAA6D,CAAA;AACxG,OAAO,EAAE,sBAAsB,EAAE,MAAM,2DAA2D,CAAA;AAElG,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,MAAM,GAAG,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAA;AAepC,SAAS,mBAAmB,CAAC,IAAiB,EAAE,WAAoB;IAClE,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,wBAAwB,EAAE,CAAA;IACnC,CAAC;IAED,MAAM,QAAQ,GAAsB,EAAE,CAAA;IACtC,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,IAAI,CAAA;IAE1E,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAA;IACxD,MAAM,MAAM,GAAG,aAAa,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAA;IACnF,IAAI,aAAa,IAAI,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,6BAA6B,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC5G,CAAC;IAED,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IACpC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,wBAAwB,EAAE,CAAA;QAC7C,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACzB,sBAAsB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAA;QACzF,GAAG,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAA;IACzD,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,uGAAuG;YACvG,4EAA4E,CAC7E,CAAA;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAA;AACnF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAA;IACtC,MAAM,kBAAkB,GAAG,+BAA+B,EAAE,CAAA;IAC5D,MAAM,eAAe,GAAG,yBAAyB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAA;IACnF,MAAM,GAAG,GAAG,OAAO,EAAE,CAAA;IACrB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,YAAY,CAAC,CAAA;IACtE,MAAM,cAAc,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;IAC/C,MAAM,WAAW,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAE9E,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IAC3B,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACvB,oBAAoB,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IAE1C,qCAAqC;IACrC,MAAM,aAAa,GAAG,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAA;IACrG,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;IACzD,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAA;IAEnE,+CAA+C;IAC/C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IACtE,aAAa,CAAC,OAAO,GAAG,eAAe,CAAA;IAEvC,IAAI,eAA4C,CAAA;IAChD,IAAI,eAA2D,CAAA;IAE/D,gDAAgD;IAChD,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClE,MAAM,EAAE,GAAG,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,aAAa,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAA;QAC7C,MAAM,QAAQ,GAAG,cAAc,CAAC,EAAE,CAAC,CAAA;QACnC,MAAM,WAAW,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAA;QAClD,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,gBAAgB,CAAC,CAAA;QACnE,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAA;QAEhD,eAAe,GAAG,mBAAmB,CACnC,EAAE,GAAG,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,EAC7D,OAAO,CAAC,WAAW,IAAI,KAAK,CAC7B,CAAA;QAED,qBAAqB,CAAC;YACpB,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB;YAClE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe;SACvD,CAAC,CAAA;QAEF,qEAAqE;QACrE,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;YACjC,aAAa,CAAC,aAAa,EAAE,CAAA;YAC7B,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAA;QACnE,CAAC,EAAE,MAAM,CAAC,CAAA;QAEV,GAAG,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAA;IACzC,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE,EAAE,6EAA6E,CAAC,CAAA;IAC5H,CAAC;IAED,cAAc;IACd,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC,iFAAiF,CAAC,CAAA;IAC7F,CAAC;IAED,IAAI,cAAc,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QAC3C,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,CAAA;QACpD,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE;YAC/C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,UAAU,KAAK;QAClB,IAAI,eAAe,EAAE,CAAC;YACpB,MAAM,eAAe,CAAC,KAAK,EAAE,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,IAAI,eAAe;YAAE,aAAa,CAAC,eAAe,CAAC,CAAA;QACnD,IAAI,eAAe;YAAE,MAAM,eAAe,CAAC,IAAI,EAAE,CAAA;QACjD,aAAa,EAAE,CAAA;IACjB,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import rateLimit from 'express-rate-limit';
|
|
6
|
+
import { createChildLogger } from '../../../lib/logger.js';
|
|
7
|
+
const log = createChildLogger('admin-routes');
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const authRateLimits = new Map();
|
|
10
|
+
const MAX_FAILURES = 5;
|
|
11
|
+
const LOCKOUT_MS = 60_000;
|
|
12
|
+
const sseTickets = new Map();
|
|
13
|
+
const TICKET_TTL_MS = 10_000;
|
|
14
|
+
// Clean up expired tickets periodically
|
|
15
|
+
setInterval(() => {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
for (const [token, ticket] of sseTickets.entries()) {
|
|
18
|
+
if (now - ticket.createdAt > TICKET_TTL_MS) {
|
|
19
|
+
sseTickets.delete(token);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, 5_000);
|
|
23
|
+
function checkAdminAuth(adminSecret, req, res) {
|
|
24
|
+
const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim() ?? req.socket.remoteAddress ?? 'unknown';
|
|
25
|
+
// Check lockout
|
|
26
|
+
const limit = authRateLimits.get(ip);
|
|
27
|
+
if (limit && Date.now() < limit.lockedUntil) {
|
|
28
|
+
const retryAfter = Math.ceil((limit.lockedUntil - Date.now()) / 1000);
|
|
29
|
+
res.status(429).json({
|
|
30
|
+
code: 'RATE_LIMITED',
|
|
31
|
+
message: 'Too many failed auth attempts. Try again later.',
|
|
32
|
+
retryable: true,
|
|
33
|
+
details: `Locked out for ${retryAfter}s`,
|
|
34
|
+
});
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const authHeader = req.headers.authorization;
|
|
38
|
+
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined;
|
|
39
|
+
if (!token || token !== adminSecret) {
|
|
40
|
+
// Track failure
|
|
41
|
+
const current = authRateLimits.get(ip) ?? { failures: 0, lockedUntil: 0 };
|
|
42
|
+
current.failures++;
|
|
43
|
+
if (current.failures >= MAX_FAILURES) {
|
|
44
|
+
current.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
45
|
+
current.failures = 0;
|
|
46
|
+
log.warn({ ip }, 'Admin auth locked out after repeated failures');
|
|
47
|
+
}
|
|
48
|
+
authRateLimits.set(ip, current);
|
|
49
|
+
res.status(401).json({
|
|
50
|
+
code: 'UNAUTHORIZED',
|
|
51
|
+
message: 'Invalid or missing admin secret',
|
|
52
|
+
retryable: false,
|
|
53
|
+
});
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Reset failures on success
|
|
57
|
+
authRateLimits.delete(ip);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
function validateDecideInput(body) {
|
|
61
|
+
const { action, matchType, duration } = body;
|
|
62
|
+
if (!action || (action !== 'approve' && action !== 'deny')) {
|
|
63
|
+
return { code: 'INVALID_ACTION', message: 'action must be "approve" or "deny"', retryable: false };
|
|
64
|
+
}
|
|
65
|
+
const decision = action === 'approve' ? 'approved' : 'denied';
|
|
66
|
+
if (decision === 'approved') {
|
|
67
|
+
if (!matchType || (matchType !== 'exact' && matchType !== 'prefix')) {
|
|
68
|
+
return { code: 'INVALID_MATCH_TYPE', message: 'matchType must be "exact" or "prefix" when approving', retryable: false };
|
|
69
|
+
}
|
|
70
|
+
if (!duration || !['2', '8', 'permanent'].includes(duration)) {
|
|
71
|
+
return { code: 'INVALID_DURATION', message: 'duration must be "2", "8", or "permanent" when approving', retryable: false };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
decision,
|
|
76
|
+
matchType: matchType ?? 'exact',
|
|
77
|
+
duration: duration ?? '0',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function isValidationError(result) {
|
|
81
|
+
return 'code' in result;
|
|
82
|
+
}
|
|
83
|
+
export function registerApprovalRoutes(deps) {
|
|
84
|
+
const { router, adminSecret, webChannel, approvalStore, auditLog } = deps;
|
|
85
|
+
// Rate limiter middleware for admin API routes
|
|
86
|
+
const adminRateLimiter = rateLimit({
|
|
87
|
+
windowMs: 60_000,
|
|
88
|
+
max: 60,
|
|
89
|
+
standardHeaders: true,
|
|
90
|
+
legacyHeaders: false,
|
|
91
|
+
message: { code: 'RATE_LIMITED', message: 'Too many requests. Try again later.', retryable: true },
|
|
92
|
+
});
|
|
93
|
+
// Load HTML page at startup -- try compiled location first, then source
|
|
94
|
+
const possiblePaths = [
|
|
95
|
+
path.join(__dirname, 'approval_page.html'),
|
|
96
|
+
path.resolve(__dirname, '../../../../server/src/domains/command-gateway/api/approval_page.html'),
|
|
97
|
+
path.resolve(process.cwd(), 'server/src/domains/command-gateway/api/approval_page.html'),
|
|
98
|
+
];
|
|
99
|
+
const htmlPath = possiblePaths.find(p => fs.existsSync(p));
|
|
100
|
+
const approvalPageHtml = htmlPath
|
|
101
|
+
? fs.readFileSync(htmlPath, 'utf8')
|
|
102
|
+
: '<html><body><h1>Approval page not found</h1></body></html>';
|
|
103
|
+
// Serve the admin HTML page (no auth - page handles login client-side)
|
|
104
|
+
router.get('/admin/approvals', (_req, res) => {
|
|
105
|
+
res.type('html').send(approvalPageHtml);
|
|
106
|
+
});
|
|
107
|
+
// List pending requests
|
|
108
|
+
router.get('/api/v1/admin/approvals/pending', adminRateLimiter, (req, res) => {
|
|
109
|
+
if (!checkAdminAuth(adminSecret, req, res))
|
|
110
|
+
return;
|
|
111
|
+
res.json({ pending: webChannel.getPendingRequests() });
|
|
112
|
+
});
|
|
113
|
+
// Exchange bearer token for one-time SSE ticket
|
|
114
|
+
router.post('/api/v1/admin/approvals/stream-ticket', adminRateLimiter, (req, res) => {
|
|
115
|
+
if (!checkAdminAuth(adminSecret, req, res))
|
|
116
|
+
return;
|
|
117
|
+
const token = randomUUID();
|
|
118
|
+
sseTickets.set(token, { token, createdAt: Date.now() });
|
|
119
|
+
res.json({ ticket: token, ttlSeconds: TICKET_TTL_MS / 1000 });
|
|
120
|
+
});
|
|
121
|
+
// SSE stream for real-time updates
|
|
122
|
+
router.get('/api/v1/admin/approvals/stream', (req, res) => {
|
|
123
|
+
const ticket = req.query.ticket;
|
|
124
|
+
if (!ticket) {
|
|
125
|
+
res.status(401).json({ code: 'UNAUTHORIZED', message: 'Missing SSE ticket', retryable: false });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const storedTicket = sseTickets.get(ticket);
|
|
129
|
+
if (!storedTicket || Date.now() - storedTicket.createdAt > TICKET_TTL_MS) {
|
|
130
|
+
sseTickets.delete(ticket);
|
|
131
|
+
res.status(401).json({ code: 'TICKET_EXPIRED', message: 'SSE ticket expired or invalid', retryable: true });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Consume ticket (one-time use)
|
|
135
|
+
sseTickets.delete(ticket);
|
|
136
|
+
res.writeHead(200, {
|
|
137
|
+
'Content-Type': 'text/event-stream',
|
|
138
|
+
'Cache-Control': 'no-cache',
|
|
139
|
+
'Connection': 'keep-alive',
|
|
140
|
+
'X-Accel-Buffering': 'no',
|
|
141
|
+
});
|
|
142
|
+
// Send initial pending list
|
|
143
|
+
const pending = webChannel.getPendingRequests();
|
|
144
|
+
res.write(`event: init\ndata: ${JSON.stringify({ pending })}\n\n`);
|
|
145
|
+
webChannel.addSSEClient(res);
|
|
146
|
+
// Heartbeat to detect broken connections
|
|
147
|
+
const heartbeat = setInterval(() => {
|
|
148
|
+
res.write(': heartbeat\n\n');
|
|
149
|
+
}, 30_000);
|
|
150
|
+
req.on('close', () => {
|
|
151
|
+
clearInterval(heartbeat);
|
|
152
|
+
webChannel.removeSSEClient(res);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
// Approve or deny a request
|
|
156
|
+
router.post('/api/v1/admin/approvals/:requestId/decide', adminRateLimiter, (req, res) => {
|
|
157
|
+
if (!checkAdminAuth(adminSecret, req, res))
|
|
158
|
+
return;
|
|
159
|
+
const requestId = Array.isArray(req.params.requestId) ? req.params.requestId[0] : req.params.requestId;
|
|
160
|
+
const validated = validateDecideInput(req.body);
|
|
161
|
+
if (isValidationError(validated)) {
|
|
162
|
+
res.status(400).json(validated);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const { decision, matchType: resolvedMatchType, duration: resolvedDuration } = validated;
|
|
166
|
+
// Try to resolve via web channel
|
|
167
|
+
const pending = webChannel.getPendingRequests().find(p => p.requestId === requestId);
|
|
168
|
+
if (!pending) {
|
|
169
|
+
res.status(409).json({
|
|
170
|
+
code: 'ALREADY_DECIDED',
|
|
171
|
+
message: 'Request already decided or expired',
|
|
172
|
+
retryable: false,
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (decision === 'approved') {
|
|
177
|
+
const approvalCommand = resolvedMatchType === 'prefix'
|
|
178
|
+
? pending.command.split(/\s+/).slice(0, 2).join(' ')
|
|
179
|
+
: pending.command;
|
|
180
|
+
approvalStore.addApproval(approvalCommand, resolvedMatchType, resolvedDuration, 'web:admin');
|
|
181
|
+
}
|
|
182
|
+
auditLog.append({
|
|
183
|
+
ts: new Date().toISOString(),
|
|
184
|
+
type: decision === 'approved' ? 'approved' : 'denied',
|
|
185
|
+
requestId,
|
|
186
|
+
command: pending.command,
|
|
187
|
+
duration: decision === 'approved' ? resolvedDuration : undefined,
|
|
188
|
+
approvedBy: 'web:admin',
|
|
189
|
+
});
|
|
190
|
+
const resolved = webChannel.resolveRequest(requestId, decision, resolvedMatchType, resolvedDuration);
|
|
191
|
+
if (!resolved) {
|
|
192
|
+
res.status(409).json({
|
|
193
|
+
code: 'ALREADY_DECIDED',
|
|
194
|
+
message: 'Request was decided by another channel',
|
|
195
|
+
retryable: false,
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
log.info({ requestId, decision, matchType: resolvedMatchType, duration: resolvedDuration }, 'Admin decision via web UI');
|
|
200
|
+
res.json({ ok: true, requestId, decision });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=register_approval_routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register_approval_routes.js","sourceRoot":"","sources":["../../../../../server/src/domains/command-gateway/api/register_approval_routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createChildLogger } from '../../../lib/logger.js';
|
|
2
|
+
const log = createChildLogger('multi-channel');
|
|
3
|
+
export function createMultiApprovalChannel(channels) {
|
|
4
|
+
if (channels.length === 0) {
|
|
5
|
+
throw new Error('At least one approval channel is required');
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
async requestApproval(command, apiKeyName, ip, requestId, riskAnalysis) {
|
|
9
|
+
const promises = channels.map(ch => ch.requestApproval(command, apiKeyName, ip, requestId, riskAnalysis));
|
|
10
|
+
const result = await Promise.race(promises);
|
|
11
|
+
// Clean up losing channels' internal state
|
|
12
|
+
for (const ch of channels) {
|
|
13
|
+
ch.cancel?.(requestId);
|
|
14
|
+
}
|
|
15
|
+
log.info({ requestId, decision: result.decision }, 'Multi-channel approval resolved');
|
|
16
|
+
return result;
|
|
17
|
+
},
|
|
18
|
+
async start() {
|
|
19
|
+
await Promise.all(channels.map(ch => ch.start()));
|
|
20
|
+
log.info({ channelCount: channels.length }, 'All approval channels started');
|
|
21
|
+
},
|
|
22
|
+
async stop() {
|
|
23
|
+
await Promise.all(channels.map(ch => ch.stop()));
|
|
24
|
+
log.info('All approval channels stopped');
|
|
25
|
+
},
|
|
26
|
+
cancel(requestId) {
|
|
27
|
+
for (const ch of channels) {
|
|
28
|
+
ch.cancel?.(requestId);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=multi_approval_channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi_approval_channel.js","sourceRoot":"","sources":["../../../../../server/src/domains/command-gateway/service/multi_approval_channel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,GAAG,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;AAE/C,MAAM,UAAU,0BAA0B,CAAC,QAA2B;IACpE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO;QACL,KAAK,CAAC,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,YAAY;YACpE,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CACjC,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,YAAY,CAAC,CACrE,CAAC;YAEF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE5C,2CAA2C;YAC3C,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,EAAE,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;YACzB,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,iCAAiC,CAAC,CAAC;YACtF,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAClD,GAAG,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,+BAA+B,CAAC,CAAC;QAC/E,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACjD,GAAG,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,CAAC,SAAiB;YACtB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,EAAE,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -32,6 +32,8 @@ export function createTelegramApprovalChannel(token, chatId, pendingStore, appro
|
|
|
32
32
|
: pending.command;
|
|
33
33
|
approvalStore.addApproval(approvalCommand, matchType, duration, `telegram:${ctx.callbackQuery.from.id}`);
|
|
34
34
|
}
|
|
35
|
+
// Store decision metadata so the Promise resolve can read the actual values
|
|
36
|
+
decisionMeta.set(requestId, { matchType: matchType, duration });
|
|
35
37
|
auditLog.append({
|
|
36
38
|
ts: new Date().toISOString(),
|
|
37
39
|
type: decision === 'approved' ? 'approved' : 'denied',
|
|
@@ -46,8 +48,31 @@ export function createTelegramApprovalChannel(token, chatId, pendingStore, appro
|
|
|
46
48
|
await ctx.answerCbQuery(label);
|
|
47
49
|
await ctx.editMessageText(`${emoji} ${label}\n\n${pending.command}`);
|
|
48
50
|
});
|
|
51
|
+
// Track per-request decision metadata from callbacks
|
|
52
|
+
const decisionMeta = new Map();
|
|
49
53
|
return {
|
|
50
54
|
async requestApproval(command, apiKeyName, ip, requestId, riskAnalysis) {
|
|
55
|
+
// Wire Promise callbacks BEFORE sending the notification to avoid race condition
|
|
56
|
+
const approvalPromise = new Promise((resolve, reject) => {
|
|
57
|
+
const pending = pendingStore.get(requestId);
|
|
58
|
+
if (!pending) {
|
|
59
|
+
reject(new Error('Request not found in pending store'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const originalResolve = pending.resolve;
|
|
63
|
+
const originalReject = pending.reject;
|
|
64
|
+
pending.resolve = (decision) => {
|
|
65
|
+
const meta = decisionMeta.get(requestId) ?? { matchType: 'exact', duration: '2' };
|
|
66
|
+
decisionMeta.delete(requestId);
|
|
67
|
+
originalResolve(decision);
|
|
68
|
+
resolve({ decision, matchType: meta.matchType, duration: meta.duration });
|
|
69
|
+
};
|
|
70
|
+
pending.reject = (reason) => {
|
|
71
|
+
decisionMeta.delete(requestId);
|
|
72
|
+
originalReject(reason);
|
|
73
|
+
reject(reason);
|
|
74
|
+
};
|
|
75
|
+
});
|
|
51
76
|
let text = `\u{1f6a8} **Command Request**\n\n`;
|
|
52
77
|
text += `From: \`${apiKeyName}\` (${ip})\n`;
|
|
53
78
|
text += `ID: \`${requestId}\`\n\n`;
|
|
@@ -85,25 +110,7 @@ export function createTelegramApprovalChannel(token, chatId, pendingStore, appro
|
|
|
85
110
|
ip,
|
|
86
111
|
});
|
|
87
112
|
log.info({ requestId, command, chatId }, 'Telegram approval request sent');
|
|
88
|
-
return
|
|
89
|
-
const pending = pendingStore.get(requestId);
|
|
90
|
-
if (!pending) {
|
|
91
|
-
reject(new Error('Request not found in pending store'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const originalResolve = pending.resolve;
|
|
95
|
-
const originalReject = pending.reject;
|
|
96
|
-
pending.resolve = (decision) => {
|
|
97
|
-
const matchType = 'exact';
|
|
98
|
-
const duration = 'unknown';
|
|
99
|
-
originalResolve(decision);
|
|
100
|
-
resolve({ decision, matchType, duration });
|
|
101
|
-
};
|
|
102
|
-
pending.reject = (reason) => {
|
|
103
|
-
originalReject(reason);
|
|
104
|
-
reject(reason);
|
|
105
|
-
};
|
|
106
|
-
});
|
|
113
|
+
return approvalPromise;
|
|
107
114
|
},
|
|
108
115
|
async start() {
|
|
109
116
|
await bot.launch();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request_telegram_approval.js","sourceRoot":"","sources":["../../../../../server/src/domains/command-gateway/service/request_telegram_approval.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"request_telegram_approval.js","sourceRoot":"","sources":["../../../../../server/src/domains/command-gateway/service/request_telegram_approval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createChildLogger } from '../../../lib/logger.js';
|
|
2
|
+
const log = createChildLogger('web-channel');
|
|
3
|
+
export function createWebApprovalChannel() {
|
|
4
|
+
const callbacks = new Map();
|
|
5
|
+
const sseClients = new Set();
|
|
6
|
+
function broadcast(event, data) {
|
|
7
|
+
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
8
|
+
for (const client of sseClients) {
|
|
9
|
+
client.write(payload);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
async requestApproval(command, apiKeyName, ip, requestId, riskAnalysis) {
|
|
14
|
+
const request = {
|
|
15
|
+
requestId,
|
|
16
|
+
command,
|
|
17
|
+
apiKeyName,
|
|
18
|
+
ip,
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
riskAnalysis,
|
|
21
|
+
};
|
|
22
|
+
const promise = new Promise((resolve, reject) => {
|
|
23
|
+
callbacks.set(requestId, { request, resolve, reject });
|
|
24
|
+
});
|
|
25
|
+
broadcast('new_request', request);
|
|
26
|
+
log.info({ requestId, command }, 'Web approval request broadcast');
|
|
27
|
+
return promise;
|
|
28
|
+
},
|
|
29
|
+
async start() {
|
|
30
|
+
log.info('Web approval channel started');
|
|
31
|
+
},
|
|
32
|
+
async stop() {
|
|
33
|
+
// Reject all pending callbacks
|
|
34
|
+
for (const [requestId, cb] of callbacks.entries()) {
|
|
35
|
+
cb.reject(new Error('Web approval channel shutting down'));
|
|
36
|
+
callbacks.delete(requestId);
|
|
37
|
+
}
|
|
38
|
+
// Close all SSE connections
|
|
39
|
+
for (const client of sseClients) {
|
|
40
|
+
client.end();
|
|
41
|
+
}
|
|
42
|
+
sseClients.clear();
|
|
43
|
+
log.info('Web approval channel stopped');
|
|
44
|
+
},
|
|
45
|
+
cancel(requestId) {
|
|
46
|
+
const cb = callbacks.get(requestId);
|
|
47
|
+
if (cb) {
|
|
48
|
+
callbacks.delete(requestId);
|
|
49
|
+
broadcast('request_decided', { requestId, decision: 'cancelled' });
|
|
50
|
+
log.debug({ requestId }, 'Web channel request cancelled (decided via another channel)');
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
getPendingRequests() {
|
|
54
|
+
return Array.from(callbacks.values()).map(cb => cb.request);
|
|
55
|
+
},
|
|
56
|
+
resolveRequest(requestId, decision, matchType, duration) {
|
|
57
|
+
const cb = callbacks.get(requestId);
|
|
58
|
+
if (!cb) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
callbacks.delete(requestId);
|
|
62
|
+
cb.resolve({ decision, matchType, duration });
|
|
63
|
+
broadcast('request_decided', { requestId, decision, matchType, duration });
|
|
64
|
+
log.info({ requestId, decision, matchType, duration }, 'Web approval resolved');
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
addSSEClient(res) {
|
|
68
|
+
sseClients.add(res);
|
|
69
|
+
log.debug({ clientCount: sseClients.size }, 'SSE client connected');
|
|
70
|
+
},
|
|
71
|
+
removeSSEClient(res) {
|
|
72
|
+
sseClients.delete(res);
|
|
73
|
+
log.debug({ clientCount: sseClients.size }, 'SSE client disconnected');
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=web_approval_channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web_approval_channel.js","sourceRoot":"","sources":["../../../../../server/src/domains/command-gateway/service/web_approval_channel.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lucifer-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-alpha.2.a4a1d3e",
|
|
4
4
|
"description": "AI agent command firewall with Telegram-based human approval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"better-sqlite3": "^12.8.0",
|
|
47
47
|
"express": "^5.2.1",
|
|
48
|
+
"express-rate-limit": "^8.3.2",
|
|
48
49
|
"pino": "^10.3.1",
|
|
49
50
|
"react": "^19.2.4",
|
|
50
51
|
"react-dom": "^19.2.4",
|