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.
Files changed (83) hide show
  1. package/LICENSE +12 -0
  2. package/README.ar.md +18 -0
  3. package/README.md +198 -1664
  4. package/bin/wab-init.js +223 -0
  5. package/examples/azure-dns-wab.js +83 -0
  6. package/examples/cloudflare-wab-dns.js +121 -0
  7. package/examples/cpanel-wab-dns.js +114 -0
  8. package/examples/dns-discovery-agent.js +166 -0
  9. package/examples/gcp-dns-wab.js +76 -0
  10. package/examples/governance-agent.js +169 -0
  11. package/examples/plesk-wab-dns.js +103 -0
  12. package/examples/route53-wab-dns.js +144 -0
  13. package/examples/safe-mode-agent.js +96 -0
  14. package/examples/wab-sign.js +74 -0
  15. package/examples/wab-verify.js +60 -0
  16. package/package.json +5 -5
  17. package/public/.well-known/wab.json +28 -0
  18. package/public/activate.html +368 -0
  19. package/public/adoption-metrics.html +188 -0
  20. package/public/api.html +1 -1
  21. package/public/azure-dns-integration.html +289 -0
  22. package/public/cloudflare-integration.html +380 -0
  23. package/public/cpanel-integration.html +398 -0
  24. package/public/css/styles.css +28 -0
  25. package/public/dashboard.html +1 -0
  26. package/public/dns.html +101 -172
  27. package/public/docs.html +1 -0
  28. package/public/gcp-dns-integration.html +318 -0
  29. package/public/growth.html +4 -2
  30. package/public/index.html +227 -31
  31. package/public/integrations.html +1 -1
  32. package/public/js/activate.js +145 -0
  33. package/public/js/auth-nav.js +34 -0
  34. package/public/js/dns.js +438 -0
  35. package/public/openapi.json +89 -0
  36. package/public/plesk-integration.html +375 -0
  37. package/public/premium.html +1 -1
  38. package/public/provider-onboarding.html +172 -0
  39. package/public/provider-sandbox.html +134 -0
  40. package/public/providers.html +359 -0
  41. package/public/registrar-integrations.html +141 -0
  42. package/public/robots.txt +12 -0
  43. package/public/route53-integration.html +531 -0
  44. package/public/shieldqr.html +231 -0
  45. package/public/sitemap.xml +6 -0
  46. package/public/wab-trust.html +200 -0
  47. package/public/wab-vs-protocols.html +210 -0
  48. package/public/whitepaper.html +449 -0
  49. package/sdk/auto-discovery.js +288 -0
  50. package/sdk/governance.js +262 -0
  51. package/sdk/index.js +13 -0
  52. package/sdk/package.json +2 -2
  53. package/sdk/safe-mode.js +221 -0
  54. package/server/index.js +144 -5
  55. package/server/migrations/007_governance.sql +106 -0
  56. package/server/migrations/008_plans.sql +144 -0
  57. package/server/migrations/009_shieldqr.sql +30 -0
  58. package/server/migrations/010_extended_trust.sql +33 -0
  59. package/server/models/adapters/mysql.js +1 -1
  60. package/server/models/adapters/postgresql.js +1 -1
  61. package/server/models/db.js +60 -1
  62. package/server/routes/admin-plans.js +76 -0
  63. package/server/routes/admin-premium.js +4 -2
  64. package/server/routes/admin-shieldqr.js +90 -0
  65. package/server/routes/admin-trust-monitor.js +83 -0
  66. package/server/routes/admin.js +289 -1
  67. package/server/routes/billing.js +16 -4
  68. package/server/routes/discovery.js +1933 -2
  69. package/server/routes/governance.js +208 -0
  70. package/server/routes/plans.js +33 -0
  71. package/server/routes/providers.js +650 -0
  72. package/server/routes/shieldqr.js +88 -0
  73. package/server/services/email.js +29 -0
  74. package/server/services/governance.js +466 -0
  75. package/server/services/plans.js +214 -0
  76. package/server/services/premium.js +1 -1
  77. package/server/services/provider-clients.js +740 -0
  78. package/server/services/shieldqr.js +322 -0
  79. package/server/services/ssl-inspector.js +42 -0
  80. package/server/services/ssl-monitor.js +167 -0
  81. package/server/services/stripe.js +18 -5
  82. package/server/services/vision.js +1 -1
  83. package/server/services/wab-crypto.js +178 -0
@@ -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;
@@ -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
- if (!siteId || !tier) return res.status(400).json({ error: 'siteId and tier required' });
15
- if (!['starter', 'pro', 'enterprise'].includes(tier)) return res.status(400).json({ error: 'Invalid tier' });
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 });