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
@@ -0,0 +1,144 @@
1
+ -- Migration 008: Plans Management
2
+ -- Database-driven plans + feature catalog so admins can add/edit plans,
3
+ -- toggle which features each plan includes, and have changes flow live to
4
+ -- the landing page pricing section AND the Stripe checkout flow.
5
+ --
6
+ -- Backwards-compatible: legacy code paths that look up tiers by slug
7
+ -- ('free' | 'starter' | 'pro' | 'enterprise') keep working — those slugs
8
+ -- are seeded as plan ids below.
9
+ --
10
+ -- An older `plans` table (different schema: tier/price/etc.) may exist from
11
+ -- a previous admin dashboard iteration. Its rows are pure default seeds with
12
+ -- no FK references, so we drop it and recreate with the new schema.
13
+
14
+ DROP TABLE IF EXISTS plans;
15
+
16
+ CREATE TABLE plans (
17
+ id TEXT PRIMARY KEY, -- slug, lowercase, e.g. 'free' / 'pro' / 'business' / 'enterprise'
18
+ name TEXT NOT NULL,
19
+ tagline TEXT,
20
+ description TEXT,
21
+ price_cents INTEGER NOT NULL DEFAULT 0,
22
+ currency TEXT NOT NULL DEFAULT 'EUR',
23
+ billing_period TEXT NOT NULL DEFAULT 'month'
24
+ CHECK(billing_period IN ('month','year','one_time','custom')),
25
+ stripe_price_id TEXT,
26
+ cta_type TEXT NOT NULL DEFAULT 'checkout'
27
+ CHECK(cta_type IN ('checkout','register','contact','external')),
28
+ cta_label TEXT,
29
+ cta_url TEXT,
30
+ highlight INTEGER NOT NULL DEFAULT 0,
31
+ is_public INTEGER NOT NULL DEFAULT 1,
32
+ is_archived INTEGER NOT NULL DEFAULT 0,
33
+ sort_order INTEGER NOT NULL DEFAULT 100,
34
+ features_json TEXT NOT NULL DEFAULT '{}',
35
+ limits_json TEXT NOT NULL DEFAULT '{}',
36
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
37
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
38
+ );
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_plans_public_archived ON plans(is_public, is_archived, sort_order);
41
+
42
+ CREATE TABLE IF NOT EXISTS feature_catalog (
43
+ feature_key TEXT PRIMARY KEY,
44
+ label TEXT NOT NULL,
45
+ description TEXT,
46
+ category TEXT NOT NULL DEFAULT 'general',
47
+ is_open_source INTEGER NOT NULL DEFAULT 0,
48
+ sort_order INTEGER NOT NULL DEFAULT 100,
49
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
50
+ );
51
+
52
+ -- Feature catalog (open-source / always-free first, then paid features)
53
+ INSERT OR IGNORE INTO feature_catalog (feature_key, label, description, category, is_open_source, sort_order) VALUES
54
+ -- Always-free / open core
55
+ ('protocol', 'WAP Protocol Core', 'Open Web Agent Protocol — schema, discovery, permissions', 'core', 1, 10),
56
+ ('sdk', 'SDK & Client Runtime', 'JavaScript SDK and client integrations', 'core', 1, 20),
57
+ ('browserExecution', 'Browser Execution Layer', 'Basic browser automation primitives', 'core', 1, 30),
58
+ ('adapters', 'MCP / REST / Browser Adapters','Adapters for MCP, REST APIs, and browser back-ends', 'core', 1, 40),
59
+ ('registryRead', 'Public Registry (read-only)', 'Browse commands, sites and templates', 'core', 1, 50),
60
+ ('agentRegistration', 'Agent Registration', 'Register agents and obtain credentials', 'core', 1, 60),
61
+ ('basicAuth', 'Basic Authentication', 'API keys and basic auth flows', 'core', 1, 70),
62
+ ('discovery', 'DNS / .well-known Discovery', 'Service discovery via DNS TXT and /.well-known/', 'core', 1, 80),
63
+ ('capabilityNegotiation', 'Capability Negotiation', 'Capability handshake between agent and site', 'core', 1, 90),
64
+ ('semanticActions', 'Semantic Actions', 'Built-in semantic actions catalog', 'core', 1,100),
65
+ ('communityTemplates', 'Community Templates', 'Public template library', 'core', 1,110),
66
+
67
+ -- Workspace / orchestration
68
+ ('workspace', 'Control Plane / Workspace', 'Web dashboard, monitoring and agent management', 'workspace', 0,200),
69
+ ('advancedOrchestration', 'Advanced Orchestration', 'Scheduling, retries, pipelines, distributed execution', 'workspace', 0,210),
70
+ ('observability', 'Observability', 'Tracing, metrics, logs and performance insights', 'workspace', 0,220),
71
+ ('failureAnalysis', 'Failure Analysis', 'Debugging tools and root-cause reports', 'workspace', 0,230),
72
+ ('replayEngine', 'Replay Engine', 'Record and replay agent runs', 'workspace', 0,240),
73
+ ('advancedAnalytics', 'Advanced Analytics', 'Detailed analytics dashboards and exports', 'workspace', 0,250),
74
+ ('dataExtraction', 'Data Extraction', 'Structured data extraction and export', 'workspace', 0,260),
75
+ ('agentMemory', 'Agent Memory Engine', 'Persistent context and long-term memory for agents', 'workspace', 0,270),
76
+ ('llmInference', 'LLM Inference', 'Built-in LLM inference via the platform', 'workspace', 0,280),
77
+
78
+ -- Premium / business
79
+ ('hostedRuntime', 'Hosted Runtime (Cloud Exec)', 'Auto-scaling hosted execution environment', 'premium', 0,300),
80
+ ('marketplace', 'Marketplace (Publish & Sell)','Publish agents and templates on the marketplace', 'premium', 0,310),
81
+ ('certification', 'Agent Certification', 'Verified agent identity badge', 'premium', 0,320),
82
+ ('trafficIntelligence', 'Traffic Intelligence', 'Agent profiling, anomaly detection and reporting', 'premium', 0,330),
83
+ ('exploitShield', 'Exploit Shield', 'Block malicious agents at the edge', 'premium', 0,340),
84
+ ('visionAnalysis', 'Vision Analysis', 'Visual page inspection (computer-vision pipeline)', 'premium', 0,350),
85
+ ('swarmExecution', 'Swarm / Multi-Agent', 'Coordinated multi-agent (swarm) execution', 'premium', 0,360),
86
+ ('auditLog', 'Audit Logs', 'Tamper-evident HMAC-chained audit history', 'premium', 0,370),
87
+ ('customDomain', 'Custom Domain / White-label', 'Serve the workspace on your own domain', 'premium', 0,380),
88
+ ('governanceLayer', 'Agent Governance Layer', 'Policies, approvals, kill switch and spend limits', 'premium', 0,390),
89
+
90
+ -- Enterprise
91
+ ('enterpriseSecurity', 'Enterprise Security', 'Request signing, IP allowlists, SSO/SAML', 'enterprise', 0,400),
92
+ ('prioritySupport', 'Priority Support', 'Dedicated SLA-backed support channel', 'enterprise', 0,410),
93
+ ('sla', 'Uptime SLA', 'Contractual uptime SLA', 'enterprise', 0,420),
94
+ ('customDevelopment', 'Custom Development', 'Bespoke engineering and integrations', 'enterprise', 0,430),
95
+ ('dedicatedInfra', 'Dedicated Infrastructure', 'Isolated single-tenant deployment', 'enterprise', 0,440);
96
+
97
+ -- Seed the four canonical plans (admin can edit/add later).
98
+ -- features_json keys MUST match feature_catalog.feature_key.
99
+ INSERT OR IGNORE INTO plans
100
+ (id, name, tagline, description, price_cents, currency, billing_period, cta_type, cta_label, cta_url, highlight, sort_order, features_json, limits_json)
101
+ VALUES
102
+ ('free',
103
+ 'Free',
104
+ 'Open-source core, forever free',
105
+ 'WAP protocol, SDK, discovery and the entire open-source surface — for developers and integrators.',
106
+ 0, 'EUR', 'month',
107
+ 'register', 'Get started for free', '/register',
108
+ 0, 10,
109
+ '{"protocol":true,"sdk":true,"browserExecution":true,"adapters":true,"registryRead":true,"agentRegistration":true,"basicAuth":true,"discovery":true,"capabilityNegotiation":true,"semanticActions":true,"communityTemplates":true}',
110
+ '{"agents":3,"tasksPerDay":50,"executionsPerDay":100,"sessions":5,"maxConcurrency":2,"replayRecordings":10,"computeMinutesPerDay":10,"storageMB":50,"webhooks":1,"customAgents":1,"apiCallsPerMinute":20}'
111
+ ),
112
+
113
+ ('pro',
114
+ 'Pro',
115
+ 'For developers shipping production agents',
116
+ 'Everything in Free plus the workspace, observability, replay engine, advanced orchestration and analytics.',
117
+ 1000, 'EUR', 'month',
118
+ 'checkout', 'Start Pro', NULL,
119
+ 1, 20,
120
+ '{"protocol":true,"sdk":true,"browserExecution":true,"adapters":true,"registryRead":true,"agentRegistration":true,"basicAuth":true,"discovery":true,"capabilityNegotiation":true,"semanticActions":true,"communityTemplates":true,"workspace":true,"advancedOrchestration":true,"observability":true,"failureAnalysis":true,"replayEngine":true,"advancedAnalytics":true,"dataExtraction":true,"agentMemory":true,"llmInference":true}',
121
+ '{"agents":25,"tasksPerDay":2000,"executionsPerDay":5000,"sessions":50,"maxConcurrency":10,"replayRecordings":500,"computeMinutesPerDay":180,"storageMB":2000,"webhooks":10,"customAgents":10,"apiCallsPerMinute":120}'
122
+ ),
123
+
124
+ ('business',
125
+ 'Business',
126
+ 'All paid features, ready for scale',
127
+ 'Everything in Pro plus hosted runtime, marketplace, vision, swarm, traffic intelligence, exploit shield, audit logs, custom domain and governance.',
128
+ 2900, 'EUR', 'month',
129
+ 'checkout', 'Start Business', NULL,
130
+ 0, 30,
131
+ '{"protocol":true,"sdk":true,"browserExecution":true,"adapters":true,"registryRead":true,"agentRegistration":true,"basicAuth":true,"discovery":true,"capabilityNegotiation":true,"semanticActions":true,"communityTemplates":true,"workspace":true,"advancedOrchestration":true,"observability":true,"failureAnalysis":true,"replayEngine":true,"advancedAnalytics":true,"dataExtraction":true,"agentMemory":true,"llmInference":true,"hostedRuntime":true,"marketplace":true,"certification":true,"trafficIntelligence":true,"exploitShield":true,"visionAnalysis":true,"swarmExecution":true,"auditLog":true,"customDomain":true,"governanceLayer":true}',
132
+ '{"agents":100,"tasksPerDay":20000,"executionsPerDay":50000,"sessions":250,"maxConcurrency":40,"replayRecordings":5000,"computeMinutesPerDay":600,"storageMB":10000,"webhooks":50,"customAgents":50,"apiCallsPerMinute":300}'
133
+ ),
134
+
135
+ ('enterprise',
136
+ 'Enterprise',
137
+ 'Custom-built for organisations',
138
+ 'Everything in Business plus enterprise security, dedicated infrastructure, custom development, priority support and a contractual uptime SLA. Pricing is tailored to your scope.',
139
+ 0, 'EUR', 'custom',
140
+ 'contact', 'Contact sales', 'mailto:sales@webagentbridge.com',
141
+ 0, 40,
142
+ '{"protocol":true,"sdk":true,"browserExecution":true,"adapters":true,"registryRead":true,"agentRegistration":true,"basicAuth":true,"discovery":true,"capabilityNegotiation":true,"semanticActions":true,"communityTemplates":true,"workspace":true,"advancedOrchestration":true,"observability":true,"failureAnalysis":true,"replayEngine":true,"advancedAnalytics":true,"dataExtraction":true,"agentMemory":true,"llmInference":true,"hostedRuntime":true,"marketplace":true,"certification":true,"trafficIntelligence":true,"exploitShield":true,"visionAnalysis":true,"swarmExecution":true,"auditLog":true,"customDomain":true,"governanceLayer":true,"enterpriseSecurity":true,"prioritySupport":true,"sla":true,"customDevelopment":true,"dedicatedInfra":true}',
143
+ '{"agents":-1,"tasksPerDay":-1,"executionsPerDay":-1,"sessions":-1,"maxConcurrency":-1,"replayRecordings":-1,"computeMinutesPerDay":-1,"storageMB":-1,"webhooks":-1,"customAgents":-1,"apiCallsPerMinute":-1}'
144
+ );
@@ -0,0 +1,30 @@
1
+ -- Migration 009: WAB ShieldQR scan history + reports
2
+ CREATE TABLE IF NOT EXISTS shieldqr_scans (
3
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4
+ url TEXT NOT NULL,
5
+ host TEXT,
6
+ level TEXT NOT NULL CHECK(level IN ('green','yellow','red')),
7
+ score INTEGER NOT NULL DEFAULT 0,
8
+ signals_json TEXT NOT NULL DEFAULT '[]',
9
+ trust_ok INTEGER NOT NULL DEFAULT 0,
10
+ ssl_ok INTEGER NOT NULL DEFAULT 0,
11
+ user_id TEXT,
12
+ ip TEXT,
13
+ user_agent TEXT,
14
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
15
+ );
16
+ CREATE INDEX IF NOT EXISTS idx_shieldqr_scans_host_created ON shieldqr_scans(host, created_at DESC);
17
+ CREATE INDEX IF NOT EXISTS idx_shieldqr_scans_level_created ON shieldqr_scans(level, created_at DESC);
18
+
19
+ CREATE TABLE IF NOT EXISTS shieldqr_reports (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ scan_id INTEGER REFERENCES shieldqr_scans(id) ON DELETE SET NULL,
22
+ url TEXT NOT NULL,
23
+ reason TEXT,
24
+ reporter_id TEXT,
25
+ reporter_ip TEXT,
26
+ status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','reviewing','resolved','rejected')),
27
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
28
+ resolved_at DATETIME
29
+ );
30
+ CREATE INDEX IF NOT EXISTS idx_shieldqr_reports_status ON shieldqr_reports(status, created_at DESC);
@@ -0,0 +1,33 @@
1
+ -- Migration 010: WAB Extended Trust — Certificate Companion & SSL Health Monitoring
2
+ -- Per-domain SSL certificate history (Certificate Transparency log) +
3
+ -- live SSL monitoring state for the trust dashboard.
4
+
5
+ CREATE TABLE IF NOT EXISTS cert_history (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ host TEXT NOT NULL,
8
+ fingerprint_sha256 TEXT NOT NULL,
9
+ issuer TEXT,
10
+ subject TEXT,
11
+ serial TEXT,
12
+ valid_from TEXT,
13
+ valid_to TEXT,
14
+ observed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
15
+ source TEXT DEFAULT 'monitor' -- 'monitor' | 'shieldqr' | 'sign'
16
+ );
17
+ CREATE INDEX IF NOT EXISTS idx_cert_history_host_observed ON cert_history(host, observed_at DESC);
18
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_cert_history_host_fp ON cert_history(host, fingerprint_sha256);
19
+
20
+ CREATE TABLE IF NOT EXISTS ssl_monitor (
21
+ host TEXT PRIMARY KEY,
22
+ fingerprint_sha256 TEXT,
23
+ issuer TEXT,
24
+ valid_to TEXT,
25
+ days_until_expiry INTEGER,
26
+ status TEXT, -- 'active' | 'expiring' | 'expired' | 'error'
27
+ error TEXT,
28
+ last_checked_at DATETIME,
29
+ last_alert_at DATETIME,
30
+ enabled INTEGER NOT NULL DEFAULT 1,
31
+ owner_user_id TEXT
32
+ );
33
+ CREATE INDEX IF NOT EXISTS idx_ssl_monitor_status ON ssl_monitor(status, valid_to);
@@ -10,7 +10,7 @@
10
10
 
11
11
  const mysql = require('mysql2/promise');
12
12
  const bcrypt = require('bcryptjs');
13
- const { v4: uuidv4 } = require('uuid');
13
+ const { randomUUID: uuidv4 } = require('crypto');
14
14
 
15
15
  const pool = mysql.createPool(process.env.DATABASE_URL);
16
16
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  const { Pool } = require('pg');
12
12
  const bcrypt = require('bcryptjs');
13
- const { v4: uuidv4 } = require('uuid');
13
+ const { randomUUID: uuidv4 } = require('crypto');
14
14
 
15
15
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });
16
16
 
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const fs = require('fs');
4
4
  const crypto = require('crypto');
5
5
  const bcrypt = require('bcryptjs');
6
- const { v4: uuidv4 } = require('uuid');
6
+ const { randomUUID: uuidv4 } = require('crypto');
7
7
  const { encryptOptional, decryptOptional } = require('../utils/secureFields');
8
8
 
9
9
  const isTest = process.env.NODE_ENV === 'test';
@@ -205,6 +205,65 @@ db.exec(`
205
205
  CREATE INDEX IF NOT EXISTS idx_wab_ads_status ON wab_ads(status);
206
206
  CREATE INDEX IF NOT EXISTS idx_ad_events_ad ON ad_events(ad_id);
207
207
  CREATE INDEX IF NOT EXISTS idx_ad_events_created ON ad_events(created_at);
208
+
209
+ -- ─── DNS Provider Account System (v1.3) ────────────────────────────
210
+ -- Stores user-supplied credentials (encrypted at rest) for managed
211
+ -- one-click WAB DNS Discovery enable/disable across DNS providers
212
+ -- and registrars. Credentials never leave the server unencrypted.
213
+ CREATE TABLE IF NOT EXISTS provider_accounts (
214
+ id TEXT PRIMARY KEY,
215
+ user_id TEXT NOT NULL,
216
+ provider_type TEXT NOT NULL CHECK(provider_type IN (
217
+ 'cloudflare','route53','azure','gcp','cpanel','plesk','godaddy','namecheap'
218
+ )),
219
+ label TEXT NOT NULL,
220
+ credentials TEXT NOT NULL,
221
+ config TEXT DEFAULT '{}',
222
+ status TEXT DEFAULT 'pending' CHECK(status IN ('pending','active','error','disabled')),
223
+ last_test_at TEXT,
224
+ last_test_ok INTEGER DEFAULT 0,
225
+ last_test_error TEXT,
226
+ last_sync_at TEXT,
227
+ domains_count INTEGER DEFAULT 0,
228
+ created_at TEXT DEFAULT (datetime('now')),
229
+ updated_at TEXT,
230
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
231
+ );
232
+ CREATE INDEX IF NOT EXISTS idx_provider_accounts_user ON provider_accounts(user_id);
233
+ CREATE INDEX IF NOT EXISTS idx_provider_accounts_type ON provider_accounts(provider_type);
234
+
235
+ CREATE TABLE IF NOT EXISTS provider_domains (
236
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
237
+ account_id TEXT NOT NULL,
238
+ domain TEXT NOT NULL,
239
+ zone_id TEXT,
240
+ wab_enabled INTEGER DEFAULT 0,
241
+ wab_record_value TEXT,
242
+ last_action TEXT,
243
+ last_action_at TEXT,
244
+ last_action_status TEXT,
245
+ last_action_error TEXT,
246
+ created_at TEXT DEFAULT (datetime('now')),
247
+ updated_at TEXT,
248
+ FOREIGN KEY (account_id) REFERENCES provider_accounts(id) ON DELETE CASCADE,
249
+ UNIQUE(account_id, domain)
250
+ );
251
+ CREATE INDEX IF NOT EXISTS idx_provider_domains_account ON provider_domains(account_id);
252
+ CREATE INDEX IF NOT EXISTS idx_provider_domains_domain ON provider_domains(domain);
253
+
254
+ CREATE TABLE IF NOT EXISTS provider_action_log (
255
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
256
+ account_id TEXT NOT NULL,
257
+ domain TEXT,
258
+ action TEXT NOT NULL,
259
+ status TEXT NOT NULL,
260
+ duration_ms INTEGER,
261
+ detail TEXT,
262
+ created_at TEXT DEFAULT (datetime('now')),
263
+ FOREIGN KEY (account_id) REFERENCES provider_accounts(id) ON DELETE CASCADE
264
+ );
265
+ CREATE INDEX IF NOT EXISTS idx_provider_action_log_account_time
266
+ ON provider_action_log(account_id, created_at DESC);
208
267
  `);
209
268
 
210
269
  function generateLicenseKey() {
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Admin Plans API — full CRUD over the plans/feature-catalog tables.
3
+ * Mounted under /api/admin/plans (auth handled in server/index.js wiring).
4
+ */
5
+ 'use strict';
6
+
7
+ const express = require('express');
8
+ const router = express.Router();
9
+ const { authenticateAdmin } = require('../middleware/adminAuth');
10
+ const { auditLog } = require('../services/security');
11
+ const plans = require('../services/plans');
12
+
13
+ router.use(authenticateAdmin);
14
+
15
+ router.get('/', (req, res) => {
16
+ res.json({
17
+ plans: plans.listPlans({ includeArchived: true }),
18
+ features: plans.listFeatures(),
19
+ });
20
+ });
21
+
22
+ router.get('/:id', (req, res) => {
23
+ const p = plans.getPlan(req.params.id);
24
+ if (!p) return res.status(404).json({ error: 'plan not found' });
25
+ res.json({ plan: p });
26
+ });
27
+
28
+ router.post('/', (req, res) => {
29
+ try {
30
+ const created = plans.createPlan(req.body || {});
31
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'plan_create',
32
+ details: { id: created.id }, ip: req.ip });
33
+ res.status(201).json({ plan: created });
34
+ } catch (err) {
35
+ res.status(400).json({ error: err.message });
36
+ }
37
+ });
38
+
39
+ router.put('/:id', (req, res) => {
40
+ try {
41
+ const updated = plans.updatePlan(req.params.id, req.body || {});
42
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'plan_update',
43
+ details: { id: req.params.id, fields: Object.keys(req.body || {}) }, ip: req.ip });
44
+ res.json({ plan: updated });
45
+ } catch (err) {
46
+ const status = /not found/i.test(err.message) ? 404 : 400;
47
+ res.status(status).json({ error: err.message });
48
+ }
49
+ });
50
+
51
+ router.put('/:id/features/:feature', (req, res) => {
52
+ try {
53
+ const included = req.body && req.body.included !== undefined ? !!req.body.included : true;
54
+ const updated = plans.setPlanFeature(req.params.id, req.params.feature, included);
55
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'plan_feature_toggle',
56
+ details: { id: req.params.id, feature: req.params.feature, included }, ip: req.ip });
57
+ res.json({ plan: updated });
58
+ } catch (err) {
59
+ const status = /not found/i.test(err.message) ? 404 : 400;
60
+ res.status(status).json({ error: err.message });
61
+ }
62
+ });
63
+
64
+ router.delete('/:id', (req, res) => {
65
+ try {
66
+ const archived = plans.deletePlan(req.params.id);
67
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id), action: 'plan_archive',
68
+ details: { id: req.params.id }, ip: req.ip, severity: 'warning' });
69
+ res.json({ plan: archived });
70
+ } catch (err) {
71
+ const status = /not found/i.test(err.message) ? 404 : 400;
72
+ res.status(status).json({ error: err.message });
73
+ }
74
+ });
75
+
76
+ module.exports = router;
@@ -472,9 +472,11 @@ router.delete('/plugins/:pluginId', (req, res) => {
472
472
  // ═══════════════════════════════════════════════════════════════════════
473
473
 
474
474
  function getTierPrices() {
475
- const plans = db.prepare('SELECT tier, price FROM plans').all();
476
475
  const prices = { free: 0, starter: 900, pro: 2900, enterprise: 9900 };
477
- for (const p of plans) prices[p.tier] = p.price;
476
+ try {
477
+ const plans = db.prepare(`SELECT id AS tier, price_cents AS price FROM plans WHERE is_archived = 0`).all();
478
+ for (const p of plans) prices[p.tier] = p.price;
479
+ } catch (_) { /* fall back to defaults */ }
478
480
  return prices;
479
481
  }
480
482
  const TIER_PRICES = new Proxy({}, { get: function(_, tier) { return getTierPrices()[tier] || 0; } });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Admin ShieldQR — moderate user-submitted reports + browse recent scans.
3
+ * GET /api/admin/shieldqr/reports?status=open
4
+ * PUT /api/admin/shieldqr/reports/:id { status }
5
+ * GET /api/admin/shieldqr/scans?host=&level=&limit=
6
+ * GET /api/admin/shieldqr/stats
7
+ */
8
+ 'use strict';
9
+
10
+ const express = require('express');
11
+ const Database = require('better-sqlite3');
12
+ const path = require('path');
13
+ const { authenticateAdmin } = require('../middleware/adminAuth');
14
+ const { auditLog } = require('../services/security');
15
+
16
+ const router = express.Router();
17
+ router.use(authenticateAdmin);
18
+
19
+ const DATA_DIR = process.env.NODE_ENV === 'test'
20
+ ? path.join(__dirname, '..', '..', 'data-test')
21
+ : (process.env.DATA_DIR || path.join(__dirname, '..', '..', 'data'));
22
+ const DB_FILE = process.env.NODE_ENV === 'test' ? 'wab-test.db' : 'wab.db';
23
+
24
+ let _db = null;
25
+ function db() {
26
+ if (!_db) { _db = new Database(path.join(DATA_DIR, DB_FILE)); }
27
+ return _db;
28
+ }
29
+
30
+ const VALID_STATUSES = new Set(['open', 'reviewing', 'resolved', 'rejected']);
31
+ const VALID_LEVELS = new Set(['green', 'yellow', 'red']);
32
+
33
+ router.get('/reports', (req, res) => {
34
+ const status = req.query.status && VALID_STATUSES.has(req.query.status) ? req.query.status : null;
35
+ const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
36
+ const sql = status
37
+ ? `SELECT r.*, s.level, s.score, s.host
38
+ FROM shieldqr_reports r
39
+ LEFT JOIN shieldqr_scans s ON s.id = r.scan_id
40
+ WHERE r.status = ? ORDER BY r.created_at DESC LIMIT ?`
41
+ : `SELECT r.*, s.level, s.score, s.host
42
+ FROM shieldqr_reports r
43
+ LEFT JOIN shieldqr_scans s ON s.id = r.scan_id
44
+ ORDER BY r.created_at DESC LIMIT ?`;
45
+ const rows = status ? db().prepare(sql).all(status, limit) : db().prepare(sql).all(limit);
46
+ res.json({ reports: rows, count: rows.length });
47
+ });
48
+
49
+ router.put('/reports/:id', (req, res) => {
50
+ const id = parseInt(req.params.id, 10);
51
+ const status = req.body && req.body.status;
52
+ if (!Number.isFinite(id) || !VALID_STATUSES.has(status)) {
53
+ return res.status(400).json({ error: 'invalid id or status' });
54
+ }
55
+ const info = db().prepare('UPDATE shieldqr_reports SET status = ? WHERE id = ?').run(status, id);
56
+ if (info.changes === 0) { return res.status(404).json({ error: 'report not found' }); }
57
+ auditLog({
58
+ actorType: 'admin', actorId: String(req.admin.id),
59
+ action: 'shieldqr_report_update',
60
+ details: { id, status }, ip: req.ip,
61
+ });
62
+ res.json({ ok: true, id, status });
63
+ });
64
+
65
+ router.get('/scans', (req, res) => {
66
+ const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
67
+ const where = []; const params = [];
68
+ if (req.query.host) { where.push('host = ?'); params.push(String(req.query.host).toLowerCase()); }
69
+ if (req.query.level && VALID_LEVELS.has(req.query.level)) {
70
+ where.push('level = ?'); params.push(req.query.level);
71
+ }
72
+ const sql = `SELECT id, url, host, level, score, trust_ok, ssl_ok, created_at
73
+ FROM shieldqr_scans
74
+ ${where.length ? 'WHERE ' + where.join(' AND ') : ''}
75
+ ORDER BY created_at DESC LIMIT ?`;
76
+ const rows = db().prepare(sql).all(...params, limit);
77
+ res.json({ scans: rows, count: rows.length });
78
+ });
79
+
80
+ router.get('/stats', (req, res) => {
81
+ const totalScans = db().prepare('SELECT COUNT(*) AS n FROM shieldqr_scans').get().n;
82
+ const byLevel = db().prepare('SELECT level, COUNT(*) AS n FROM shieldqr_scans GROUP BY level').all();
83
+ const reportsByStatus = db().prepare('SELECT status, COUNT(*) AS n FROM shieldqr_reports GROUP BY status').all();
84
+ const recent24h = db().prepare(
85
+ "SELECT COUNT(*) AS n FROM shieldqr_scans WHERE created_at >= datetime('now','-1 day')"
86
+ ).get().n;
87
+ res.json({ totalScans, recent24h, byLevel, reportsByStatus });
88
+ });
89
+
90
+ module.exports = router;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Admin Trust Monitor — SSL health + cert history (Extended Trust Layer).
3
+ * GET /api/admin/trust-monitor/sites — list ssl_monitor rows
4
+ * POST /api/admin/trust-monitor/check { host } — re-check one host
5
+ * POST /api/admin/trust-monitor/sweep — re-check every site
6
+ * GET /api/admin/trust-monitor/history ?host= — cert_history rows
7
+ * GET /api/admin/trust-monitor/stats
8
+ */
9
+ 'use strict';
10
+
11
+ const express = require('express');
12
+ const Database = require('better-sqlite3');
13
+ const path = require('path');
14
+ const { authenticateAdmin } = require('../middleware/adminAuth');
15
+ const { auditLog } = require('../services/security');
16
+ const monitor = require('../services/ssl-monitor');
17
+
18
+ const router = express.Router();
19
+ router.use(authenticateAdmin);
20
+
21
+ const DATA_DIR = process.env.NODE_ENV === 'test'
22
+ ? path.join(__dirname, '..', '..', 'data-test')
23
+ : (process.env.DATA_DIR || path.join(__dirname, '..', '..', 'data'));
24
+ const DB_FILE = process.env.NODE_ENV === 'test' ? 'wab-test.db' : 'wab.db';
25
+
26
+ let _db = null;
27
+ function db() { if (!_db) { _db = new Database(path.join(DATA_DIR, DB_FILE)); } return _db; }
28
+
29
+ router.get('/sites', (req, res) => {
30
+ const rows = db().prepare(`
31
+ SELECT host, fingerprint_sha256, issuer, valid_to, days_until_expiry,
32
+ status, error, last_checked_at, last_alert_at
33
+ FROM ssl_monitor ORDER BY
34
+ CASE status WHEN 'expired' THEN 0 WHEN 'error' THEN 1 WHEN 'expiring' THEN 2 ELSE 3 END,
35
+ days_until_expiry ASC
36
+ `).all();
37
+ res.json({ sites: rows, count: rows.length });
38
+ });
39
+
40
+ router.post('/check', async (req, res) => {
41
+ const host = (req.body && req.body.host || '').trim().toLowerCase();
42
+ if (!host) return res.status(400).json({ error: 'host required' });
43
+ try {
44
+ const r = await monitor.checkHost(host, { source: 'admin' });
45
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id),
46
+ action: 'trust_check', details: { host, status: r.status }, ip: req.ip });
47
+ res.json(r);
48
+ } catch (e) { res.status(500).json({ error: e.message }); }
49
+ });
50
+
51
+ router.post('/sweep', async (req, res) => {
52
+ try {
53
+ const rs = await monitor.runSweep();
54
+ auditLog({ actorType: 'admin', actorId: String(req.admin.id),
55
+ action: 'trust_sweep', details: { count: rs.length }, ip: req.ip });
56
+ res.json({ count: rs.length, results: rs });
57
+ } catch (e) { res.status(500).json({ error: e.message }); }
58
+ });
59
+
60
+ router.get('/history', (req, res) => {
61
+ const host = (req.query.host || '').toLowerCase();
62
+ const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
63
+ const sql = host
64
+ ? `SELECT * FROM cert_history WHERE host = ? ORDER BY observed_at DESC LIMIT ?`
65
+ : `SELECT * FROM cert_history ORDER BY observed_at DESC LIMIT ?`;
66
+ const rows = host ? db().prepare(sql).all(host, limit) : db().prepare(sql).all(limit);
67
+ res.json({ history: rows, count: rows.length });
68
+ });
69
+
70
+ router.get('/stats', (req, res) => {
71
+ const total = db().prepare('SELECT COUNT(*) AS n FROM ssl_monitor').get().n;
72
+ const byStatus = db().prepare('SELECT status, COUNT(*) AS n FROM ssl_monitor GROUP BY status').all();
73
+ const expiringSoon = db().prepare(
74
+ "SELECT COUNT(*) AS n FROM ssl_monitor WHERE status = 'expiring'"
75
+ ).get().n;
76
+ const expired = db().prepare(
77
+ "SELECT COUNT(*) AS n FROM ssl_monitor WHERE status = 'expired'"
78
+ ).get().n;
79
+ const certHistory = db().prepare('SELECT COUNT(*) AS n FROM cert_history').get().n;
80
+ res.json({ total, byStatus, expiringSoon, expired, certHistory });
81
+ });
82
+
83
+ module.exports = router;