web-agent-bridge 3.3.0 → 3.4.0
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/LICENSE +12 -0
- package/README.ar.md +18 -0
- package/README.md +198 -1664
- package/bin/wab-init.js +223 -0
- package/examples/azure-dns-wab.js +83 -0
- package/examples/cloudflare-wab-dns.js +121 -0
- package/examples/cpanel-wab-dns.js +114 -0
- package/examples/dns-discovery-agent.js +166 -0
- package/examples/gcp-dns-wab.js +76 -0
- package/examples/governance-agent.js +169 -0
- package/examples/plesk-wab-dns.js +103 -0
- package/examples/route53-wab-dns.js +144 -0
- package/examples/safe-mode-agent.js +96 -0
- package/examples/wab-sign.js +74 -0
- package/examples/wab-verify.js +60 -0
- package/package.json +5 -5
- package/public/.well-known/wab.json +28 -0
- package/public/activate.html +368 -0
- package/public/adoption-metrics.html +188 -0
- package/public/api.html +1 -1
- package/public/azure-dns-integration.html +289 -0
- package/public/cloudflare-integration.html +380 -0
- package/public/cpanel-integration.html +398 -0
- package/public/css/styles.css +28 -0
- package/public/dashboard.html +1 -0
- package/public/dns.html +101 -172
- package/public/docs.html +1 -0
- package/public/gcp-dns-integration.html +318 -0
- package/public/growth.html +4 -2
- package/public/index.html +227 -31
- package/public/integrations.html +1 -1
- package/public/js/activate.js +145 -0
- package/public/js/auth-nav.js +34 -0
- package/public/js/dns.js +438 -0
- package/public/openapi.json +89 -0
- package/public/plesk-integration.html +375 -0
- package/public/premium.html +1 -1
- package/public/provider-onboarding.html +172 -0
- package/public/provider-sandbox.html +134 -0
- package/public/providers.html +359 -0
- package/public/registrar-integrations.html +141 -0
- package/public/robots.txt +12 -0
- package/public/route53-integration.html +531 -0
- package/public/shieldqr.html +231 -0
- package/public/sitemap.xml +6 -0
- package/public/wab-trust.html +200 -0
- package/public/wab-vs-protocols.html +210 -0
- package/public/whitepaper.html +449 -0
- package/sdk/auto-discovery.js +288 -0
- package/sdk/governance.js +262 -0
- package/sdk/index.js +13 -0
- package/sdk/package.json +2 -2
- package/sdk/safe-mode.js +221 -0
- package/server/index.js +144 -5
- package/server/migrations/007_governance.sql +106 -0
- package/server/migrations/008_plans.sql +144 -0
- package/server/migrations/009_shieldqr.sql +30 -0
- package/server/migrations/010_extended_trust.sql +33 -0
- package/server/models/adapters/mysql.js +1 -1
- package/server/models/adapters/postgresql.js +1 -1
- package/server/models/db.js +60 -1
- package/server/routes/admin-plans.js +76 -0
- package/server/routes/admin-premium.js +4 -2
- package/server/routes/admin-shieldqr.js +90 -0
- package/server/routes/admin-trust-monitor.js +83 -0
- package/server/routes/admin.js +289 -1
- package/server/routes/billing.js +16 -4
- package/server/routes/discovery.js +1933 -2
- package/server/routes/governance.js +208 -0
- package/server/routes/plans.js +33 -0
- package/server/routes/providers.js +650 -0
- package/server/routes/shieldqr.js +88 -0
- package/server/services/email.js +29 -0
- package/server/services/governance.js +466 -0
- package/server/services/plans.js +214 -0
- package/server/services/premium.js +1 -1
- package/server/services/provider-clients.js +740 -0
- package/server/services/shieldqr.js +322 -0
- package/server/services/ssl-inspector.js +42 -0
- package/server/services/ssl-monitor.js +167 -0
- package/server/services/stripe.js +18 -5
- package/server/services/vision.js +1 -1
- package/server/services/wab-crypto.js +178 -0
package/server/routes/admin.js
CHANGED
|
@@ -18,7 +18,8 @@ const {
|
|
|
18
18
|
findUserByEmail,
|
|
19
19
|
findSiteById,
|
|
20
20
|
getAnalyticsBySite,
|
|
21
|
-
getAnalyticsTimeline
|
|
21
|
+
getAnalyticsTimeline,
|
|
22
|
+
db
|
|
22
23
|
} = require('../models/db');
|
|
23
24
|
const { sendEmail } = require('../services/email');
|
|
24
25
|
const { createCheckoutSession, createPortalSession, isStripeConfigured, getStripePrices } = require('../services/stripe');
|
|
@@ -258,4 +259,291 @@ router.post('/notifications/send', authenticateAdmin, (req, res) => {
|
|
|
258
259
|
});
|
|
259
260
|
});
|
|
260
261
|
|
|
262
|
+
// ─── DNS Discovery Oversight ──────────────────────────────────────────
|
|
263
|
+
// Real-data views into discovery_usage_runs / discovery_trust_runs.
|
|
264
|
+
// Tables are auto-created by routes/discovery.js.
|
|
265
|
+
|
|
266
|
+
function tableExists(name) {
|
|
267
|
+
try {
|
|
268
|
+
return !!db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`).get(name);
|
|
269
|
+
} catch { return false; }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
router.get('/discovery/stats', authenticateAdmin, (_req, res) => {
|
|
273
|
+
if (!tableExists('discovery_usage_runs')) {
|
|
274
|
+
return res.json({ enabled: false, totals: {}, last7d: [], topDomains: [] });
|
|
275
|
+
}
|
|
276
|
+
const totals = db.prepare(`
|
|
277
|
+
SELECT
|
|
278
|
+
COUNT(*) AS runs,
|
|
279
|
+
COUNT(DISTINCT domain) AS domains,
|
|
280
|
+
SUM(CASE WHEN execution_succeeded = 1 THEN 1 ELSE 0 END) AS successes,
|
|
281
|
+
AVG(value_score) AS avg_score
|
|
282
|
+
FROM discovery_usage_runs
|
|
283
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
284
|
+
`).get();
|
|
285
|
+
const last7d = db.prepare(`
|
|
286
|
+
SELECT date(created_at) AS day, COUNT(*) AS runs,
|
|
287
|
+
SUM(CASE WHEN execution_succeeded = 1 THEN 1 ELSE 0 END) AS successes
|
|
288
|
+
FROM discovery_usage_runs
|
|
289
|
+
WHERE created_at >= datetime('now', '-7 days')
|
|
290
|
+
GROUP BY day ORDER BY day ASC
|
|
291
|
+
`).all();
|
|
292
|
+
const topDomains = db.prepare(`
|
|
293
|
+
SELECT domain, COUNT(*) AS runs, AVG(value_score) AS score
|
|
294
|
+
FROM discovery_usage_runs
|
|
295
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
296
|
+
GROUP BY domain ORDER BY runs DESC LIMIT 25
|
|
297
|
+
`).all();
|
|
298
|
+
res.json({ enabled: true, totals, last7d, topDomains });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
router.get('/discovery/runs', authenticateAdmin, (req, res) => {
|
|
302
|
+
if (!tableExists('discovery_usage_runs')) return res.json({ runs: [] });
|
|
303
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
|
|
304
|
+
const runs = db.prepare(`
|
|
305
|
+
SELECT id, domain, mode, preferred_use_case, selected_action,
|
|
306
|
+
readiness_ok, execution_attempted, execution_succeeded,
|
|
307
|
+
value_score, end_to_end_ms, created_at
|
|
308
|
+
FROM discovery_usage_runs
|
|
309
|
+
ORDER BY created_at DESC LIMIT ?
|
|
310
|
+
`).all(limit);
|
|
311
|
+
res.json({ runs });
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
router.get('/trust/stats', authenticateAdmin, (_req, res) => {
|
|
315
|
+
if (!tableExists('discovery_trust_runs')) {
|
|
316
|
+
return res.json({ enabled: false, totals: {}, leaderboard: [] });
|
|
317
|
+
}
|
|
318
|
+
const totals = db.prepare(`
|
|
319
|
+
SELECT COUNT(*) AS runs, COUNT(DISTINCT domain) AS domains,
|
|
320
|
+
AVG(score) AS avg_score,
|
|
321
|
+
SUM(sig_valid) AS valid_sigs,
|
|
322
|
+
SUM(signed_manifest) AS signed_manifests,
|
|
323
|
+
SUM(has_pk) AS domains_with_pk
|
|
324
|
+
FROM discovery_trust_runs
|
|
325
|
+
WHERE created_at >= datetime('now', '-30 days')
|
|
326
|
+
`).get();
|
|
327
|
+
const leaderboard = db.prepare(`
|
|
328
|
+
SELECT domain, MAX(score) AS score, MAX(created_at) AS last_run
|
|
329
|
+
FROM discovery_trust_runs
|
|
330
|
+
GROUP BY domain ORDER BY score DESC, last_run DESC LIMIT 25
|
|
331
|
+
`).all();
|
|
332
|
+
res.json({ enabled: true, totals, leaderboard });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
router.get('/trust/runs', authenticateAdmin, (req, res) => {
|
|
336
|
+
if (!tableExists('discovery_trust_runs')) return res.json({ runs: [] });
|
|
337
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
|
|
338
|
+
const runs = db.prepare(`
|
|
339
|
+
SELECT id, domain, score, dnssec, has_pk, signed_manifest, sig_valid, https_ok, created_at
|
|
340
|
+
FROM discovery_trust_runs
|
|
341
|
+
ORDER BY created_at DESC LIMIT ?
|
|
342
|
+
`).all(limit);
|
|
343
|
+
res.json({ runs });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// ─── Providers Oversight (cross-user) ─────────────────────────────────
|
|
347
|
+
|
|
348
|
+
router.get('/providers/stats', authenticateAdmin, (_req, res) => {
|
|
349
|
+
if (!tableExists('provider_accounts')) {
|
|
350
|
+
return res.json({ enabled: false, totals: {}, byProvider: [], recentActions: [] });
|
|
351
|
+
}
|
|
352
|
+
const totals = db.prepare(`
|
|
353
|
+
SELECT COUNT(*) AS accounts,
|
|
354
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) AS active,
|
|
355
|
+
SUM(CASE WHEN status='error' THEN 1 ELSE 0 END) AS errored,
|
|
356
|
+
SUM(domains_count) AS domains_under_management
|
|
357
|
+
FROM provider_accounts
|
|
358
|
+
`).get();
|
|
359
|
+
const byProvider = db.prepare(`
|
|
360
|
+
SELECT provider_type, COUNT(*) AS accounts,
|
|
361
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) AS active,
|
|
362
|
+
COALESCE(SUM(domains_count), 0) AS domains
|
|
363
|
+
FROM provider_accounts
|
|
364
|
+
GROUP BY provider_type ORDER BY accounts DESC
|
|
365
|
+
`).all();
|
|
366
|
+
const recentActions = tableExists('provider_action_log')
|
|
367
|
+
? db.prepare(`
|
|
368
|
+
SELECT l.id, l.account_id, a.provider_type, l.domain, l.action, l.status,
|
|
369
|
+
l.duration_ms, l.detail, l.created_at
|
|
370
|
+
FROM provider_action_log l
|
|
371
|
+
LEFT JOIN provider_accounts a ON a.id = l.account_id
|
|
372
|
+
ORDER BY l.created_at DESC LIMIT 50
|
|
373
|
+
`).all()
|
|
374
|
+
: [];
|
|
375
|
+
const wabEnabled = tableExists('provider_domains')
|
|
376
|
+
? db.prepare(`SELECT COUNT(*) AS c FROM provider_domains WHERE wab_enabled = 1`).get().c
|
|
377
|
+
: 0;
|
|
378
|
+
res.json({ enabled: true, totals: { ...totals, wab_enabled_domains: wabEnabled }, byProvider, recentActions });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
router.get('/providers/accounts', authenticateAdmin, (req, res) => {
|
|
382
|
+
if (!tableExists('provider_accounts')) return res.json({ accounts: [] });
|
|
383
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 1000);
|
|
384
|
+
const rows = db.prepare(`
|
|
385
|
+
SELECT a.id, a.user_id, u.email AS user_email, a.provider_type, a.label,
|
|
386
|
+
a.status, a.last_test_at, a.last_test_ok, a.last_test_error,
|
|
387
|
+
a.last_sync_at, a.domains_count, a.created_at, a.updated_at
|
|
388
|
+
FROM provider_accounts a
|
|
389
|
+
LEFT JOIN users u ON u.id = a.user_id
|
|
390
|
+
ORDER BY a.created_at DESC LIMIT ?
|
|
391
|
+
`).all(limit);
|
|
392
|
+
res.json({ accounts: rows });
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
router.get('/providers/domains', authenticateAdmin, (req, res) => {
|
|
396
|
+
if (!tableExists('provider_domains')) return res.json({ domains: [] });
|
|
397
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 1000);
|
|
398
|
+
const rows = db.prepare(`
|
|
399
|
+
SELECT d.id, d.account_id, a.provider_type, a.user_id, u.email AS user_email,
|
|
400
|
+
d.domain, d.zone_id, d.wab_enabled, d.wab_record_value,
|
|
401
|
+
d.last_action, d.last_action_at, d.last_action_status, d.last_action_error
|
|
402
|
+
FROM provider_domains d
|
|
403
|
+
LEFT JOIN provider_accounts a ON a.id = d.account_id
|
|
404
|
+
LEFT JOIN users u ON u.id = a.user_id
|
|
405
|
+
ORDER BY d.updated_at DESC LIMIT ?
|
|
406
|
+
`).all(limit);
|
|
407
|
+
res.json({ domains: rows });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ─── API Modules: real counts (replaces hardcoded dashboard placeholders) ──
|
|
411
|
+
router.get('/modules/stats', authenticateAdmin, (_req, res) => {
|
|
412
|
+
// Source of truth: server/routes/* + service registry. We treat each
|
|
413
|
+
// top-level route module as one "API module" and return real numbers.
|
|
414
|
+
const modules = [
|
|
415
|
+
{ id: 'auth', label: 'Authentication', openness: 'open' },
|
|
416
|
+
{ id: 'wab', label: 'WAB Core API', openness: 'open' },
|
|
417
|
+
{ id: 'discovery', label: 'DNS Discovery', openness: 'open' },
|
|
418
|
+
{ id: 'sovereign', label: 'Sovereign Mode', openness: 'open' },
|
|
419
|
+
{ id: 'commander', label: 'Commander SDK', openness: 'partial' },
|
|
420
|
+
{ id: 'mesh', label: 'Agent Mesh', openness: 'partial' },
|
|
421
|
+
{ id: 'workspace', label: 'Agent Workspace', openness: 'partial' },
|
|
422
|
+
{ id: 'universal', label: 'Universal Agent', openness: 'partial' },
|
|
423
|
+
{ id: 'gateway', label: 'Gateway (v1)', openness: 'closed' },
|
|
424
|
+
{ id: 'premium', label: 'Premium APIs', openness: 'closed' },
|
|
425
|
+
{ id: 'admin', label: 'Admin APIs', openness: 'closed' },
|
|
426
|
+
{ id: 'providers', label: 'DNS Providers', openness: 'open' },
|
|
427
|
+
];
|
|
428
|
+
const counts = modules.reduce((acc, m) => { acc[m.openness] = (acc[m.openness] || 0) + 1; return acc; }, {});
|
|
429
|
+
res.json({
|
|
430
|
+
total: modules.length,
|
|
431
|
+
open: counts.open || 0,
|
|
432
|
+
partial: counts.partial || 0,
|
|
433
|
+
closed: counts.closed || 0,
|
|
434
|
+
modules,
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// ─── Governance Layer (Phase 20) — admin oversight of all agents ──
|
|
439
|
+
// These endpoints expose the same data as /api/governance/* but authenticate
|
|
440
|
+
// via admin token instead of an agent's own token, so an admin can see and
|
|
441
|
+
// act across every agent on the platform.
|
|
442
|
+
const gov = (() => { try { return require('../services/governance'); } catch { return null; } });
|
|
443
|
+
function ensureGovTables() {
|
|
444
|
+
return tableExists('gov_agents');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
router.get('/governance/stats', authenticateAdmin, (_req, res) => {
|
|
448
|
+
if (!ensureGovTables()) return res.json({ enabled: false, totals: {} });
|
|
449
|
+
const totals = db.prepare(`
|
|
450
|
+
SELECT
|
|
451
|
+
COUNT(*) AS agents,
|
|
452
|
+
SUM(CASE WHEN status='alive' THEN 1 ELSE 0 END) AS alive,
|
|
453
|
+
SUM(CASE WHEN status='killed' THEN 1 ELSE 0 END) AS killed,
|
|
454
|
+
SUM(CASE WHEN status='suspended' THEN 1 ELSE 0 END) AS suspended
|
|
455
|
+
FROM gov_agents
|
|
456
|
+
`).get() || {};
|
|
457
|
+
const policies = db.prepare(`SELECT COUNT(*) AS c FROM gov_policies`).get().c;
|
|
458
|
+
const audit = db.prepare(`SELECT COUNT(*) AS c FROM gov_audit`).get().c;
|
|
459
|
+
const pending = db.prepare(`SELECT COUNT(*) AS c FROM gov_approvals WHERE status='pending'`).get().c;
|
|
460
|
+
const recentDenies = db.prepare(`
|
|
461
|
+
SELECT id, agent_id, ts, resource, action, decision, reason
|
|
462
|
+
FROM gov_audit WHERE decision='deny' ORDER BY id DESC LIMIT 25
|
|
463
|
+
`).all();
|
|
464
|
+
res.json({ enabled: true, totals: { ...totals, policies, audit_events: audit, pending_approvals: pending }, recentDenies });
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
router.get('/governance/agents', authenticateAdmin, (_req, res) => {
|
|
468
|
+
if (!ensureGovTables()) return res.json({ agents: [] });
|
|
469
|
+
const rows = db.prepare(`
|
|
470
|
+
SELECT a.agent_id, a.owner_id, u.email AS owner_email, a.display_name,
|
|
471
|
+
a.status, a.killed_at, a.killed_reason, a.created_at, a.updated_at,
|
|
472
|
+
(SELECT COUNT(*) FROM gov_policies p WHERE p.agent_id = a.agent_id) AS policies,
|
|
473
|
+
(SELECT COUNT(*) FROM gov_audit g WHERE g.agent_id = a.agent_id) AS audit_events,
|
|
474
|
+
(SELECT COUNT(*) FROM gov_approvals ap WHERE ap.agent_id = a.agent_id AND ap.status='pending') AS pending_approvals
|
|
475
|
+
FROM gov_agents a
|
|
476
|
+
LEFT JOIN users u ON u.id = a.owner_id
|
|
477
|
+
ORDER BY a.created_at DESC
|
|
478
|
+
`).all();
|
|
479
|
+
res.json({ agents: rows });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
router.get('/governance/agents/:id', authenticateAdmin, (req, res) => {
|
|
483
|
+
if (!ensureGovTables()) return res.status(404).json({ error: 'governance_disabled' });
|
|
484
|
+
const a = db.prepare(`SELECT agent_id, owner_id, display_name, status, killed_at, killed_reason, metadata, created_at, updated_at FROM gov_agents WHERE agent_id = ?`).get(req.params.id);
|
|
485
|
+
if (!a) return res.status(404).json({ error: 'agent_not_found' });
|
|
486
|
+
const policies = db.prepare(`SELECT * FROM gov_policies WHERE agent_id = ? ORDER BY id DESC`).all(req.params.id);
|
|
487
|
+
const approvals = db.prepare(`SELECT * FROM gov_approvals WHERE agent_id = ? AND status='pending' ORDER BY created_at DESC LIMIT 50`).all(req.params.id);
|
|
488
|
+
const audit = db.prepare(`SELECT id, ts, event_type, resource, action, scope, amount, currency, decision, reason FROM gov_audit WHERE agent_id = ? ORDER BY id DESC LIMIT 200`).all(req.params.id);
|
|
489
|
+
res.json({ agent: a, policies, approvals, audit });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
router.get('/governance/agents/:id/audit/verify', authenticateAdmin, (req, res) => {
|
|
493
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
494
|
+
res.json(g.verifyAuditChain(req.params.id));
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
router.post('/governance/agents/:id/kill', authenticateAdmin, (req, res) => {
|
|
498
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
499
|
+
const reason = (req.body && req.body.reason) || ('admin:' + (req.admin.email || req.admin.id));
|
|
500
|
+
const ok = g.killAgent(req.params.id, reason);
|
|
501
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_kill', details: { agent_id: req.params.id, reason } });
|
|
502
|
+
res.json({ ok, status: 'killed', reason });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
router.post('/governance/agents/:id/revive', authenticateAdmin, (req, res) => {
|
|
506
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
507
|
+
const reason = (req.body && req.body.reason) || ('admin:' + (req.admin.email || req.admin.id));
|
|
508
|
+
const ok = g.reviveAgent(req.params.id, reason);
|
|
509
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_revive', details: { agent_id: req.params.id, reason } });
|
|
510
|
+
res.json({ ok, status: 'alive', reason });
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
router.post('/governance/agents/:id/policies', authenticateAdmin, (req, res) => {
|
|
514
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
515
|
+
const b = req.body || {};
|
|
516
|
+
if (!b.resource || !b.action) return res.status(400).json({ error: 'missing_fields', need: ['resource', 'action'] });
|
|
517
|
+
const r = g.definePolicy({
|
|
518
|
+
agentId: req.params.id, resource: String(b.resource), action: String(b.action),
|
|
519
|
+
scope: b.scope || null, maxAmount: b.max_amount, currency: b.currency,
|
|
520
|
+
dailyCap: b.daily_cap, perCallRate: b.per_call_rate,
|
|
521
|
+
requiresApproval: !!b.requires_approval,
|
|
522
|
+
effect: b.effect === 'deny' ? 'deny' : 'allow',
|
|
523
|
+
expiresAt: b.expires_at || null,
|
|
524
|
+
});
|
|
525
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_policy_create', details: { agent_id: req.params.id, policy_id: r.id } });
|
|
526
|
+
res.status(201).json({ ok: true, id: r.id });
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
router.delete('/governance/agents/:id/policies/:pid', authenticateAdmin, (req, res) => {
|
|
530
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
531
|
+
const ok = g.deletePolicy(req.params.id, Number(req.params.pid));
|
|
532
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_policy_delete', details: { agent_id: req.params.id, policy_id: req.params.pid } });
|
|
533
|
+
res.json({ ok });
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
router.post('/governance/approvals/:rid/decide', authenticateAdmin, (req, res) => {
|
|
537
|
+
const g = gov(); if (!g) return res.status(503).json({ error: 'governance_unavailable' });
|
|
538
|
+
const decision = req.body?.decision === 'approved' ? 'approved' : 'rejected';
|
|
539
|
+
const out = g.decideApproval(req.params.rid, {
|
|
540
|
+
decision,
|
|
541
|
+
decidedBy: 'admin:' + req.admin.id,
|
|
542
|
+
note: req.body?.note || null,
|
|
543
|
+
});
|
|
544
|
+
if (!out.ok) return res.status(409).json(out);
|
|
545
|
+
auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'governance_approval_decide', details: { request_id: req.params.rid, decision } });
|
|
546
|
+
res.json(out);
|
|
547
|
+
});
|
|
548
|
+
|
|
261
549
|
module.exports = router;
|
package/server/routes/billing.js
CHANGED
|
@@ -7,19 +7,31 @@ const router = express.Router();
|
|
|
7
7
|
const { authenticateToken } = require('../middleware/auth');
|
|
8
8
|
const { getPlatformSetting } = require('../models/db');
|
|
9
9
|
const { createCheckoutSession, createPortalSession, isStripeConfigured } = require('../services/stripe');
|
|
10
|
+
const plansService = require('../services/plans');
|
|
10
11
|
|
|
11
12
|
// ─── Create Checkout Session ──────────────────────────────────────────
|
|
12
13
|
router.post('/checkout', authenticateToken, async (req, res) => {
|
|
13
|
-
const { siteId, tier } = req.body;
|
|
14
|
-
|
|
15
|
-
if (!
|
|
14
|
+
const { siteId, tier, planId } = req.body;
|
|
15
|
+
const planRef = planId || tier;
|
|
16
|
+
if (!siteId || !planRef) return res.status(400).json({ error: 'siteId and planId (or tier) required' });
|
|
17
|
+
|
|
18
|
+
// Validate against DB plans first; fall back to legacy tier whitelist if the
|
|
19
|
+
// plans table is not yet populated (e.g. fresh install before migration).
|
|
20
|
+
const dbPlan = plansService.getPlan(planRef);
|
|
21
|
+
if (dbPlan) {
|
|
22
|
+
if (dbPlan.is_archived || dbPlan.cta_type !== 'checkout') {
|
|
23
|
+
return res.status(400).json({ error: 'plan is not purchasable' });
|
|
24
|
+
}
|
|
25
|
+
} else if (!['starter', 'pro', 'business', 'enterprise'].includes(planRef)) {
|
|
26
|
+
return res.status(400).json({ error: 'Invalid plan' });
|
|
27
|
+
}
|
|
16
28
|
|
|
17
29
|
if (!isStripeConfigured()) {
|
|
18
30
|
return res.status(503).json({ error: 'Payment system not configured' });
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
try {
|
|
22
|
-
const session = await createCheckoutSession({ userId: req.user.id, userEmail: req.user.email, siteId, tier });
|
|
34
|
+
const session = await createCheckoutSession({ userId: req.user.id, userEmail: req.user.email, siteId, tier: planRef, planId: planRef });
|
|
23
35
|
res.json(session);
|
|
24
36
|
} catch (err) {
|
|
25
37
|
res.status(500).json({ error: err.message });
|