web-agent-bridge 3.2.0 → 3.3.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 +72 -72
- package/README.ar.md +1286 -1152
- package/README.md +1764 -1635
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -138
- package/bin/wab.js +80 -80
- package/examples/bidi-agent.js +119 -119
- package/examples/cross-site-agent.js +91 -91
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/puppeteer-agent.js +108 -108
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +16 -3
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -0
- package/public/agent-workspace.html +349 -349
- package/public/ai.html +198 -198
- package/public/api.html +413 -412
- package/public/browser.html +486 -486
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1235 -1235
- package/public/dashboard.html +706 -706
- package/public/dns.html +507 -0
- package/public/docs.html +587 -587
- package/public/feed.xml +89 -89
- package/public/growth.html +463 -463
- package/public/index.html +1070 -982
- package/public/integrations.html +556 -0
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +31 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/openapi.json +580 -580
- package/public/phone-shield.html +281 -0
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/register.html +105 -105
- package/public/robots.txt +87 -87
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -0
- package/public/terms.html +256 -256
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/commander.js +262 -262
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +12 -1
- package/sdk/multi-agent.js +318 -318
- package/sdk/package.json +1 -1
- package/sdk/safety-shield.js +219 -0
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +367 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +531 -427
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +681 -681
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/admin-premium.js +671 -671
- package/server/routes/admin.js +261 -261
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +45 -45
- package/server/routes/commander.js +316 -316
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -0
- package/server/routes/discovery.js +417 -417
- package/server/routes/gateway.js +173 -157
- package/server/routes/license.js +251 -240
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/runtime.js +2148 -2147
- package/server/routes/sovereign.js +465 -385
- package/server/routes/universal.js +200 -185
- package/server/routes/wab-api.js +850 -501
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/security/cross-site-redactor.js +196 -0
- package/server/security/dry-run.js +180 -0
- package/server/security/human-gate-rate-limit.js +147 -0
- package/server/security/human-gate-transports.js +178 -0
- package/server/security/human-gate.js +281 -0
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -0
- package/server/security/reward-guard.js +171 -0
- package/server/security/rollback-store.js +239 -0
- package/server/security/token-scope.js +404 -0
- package/server/security/url-policy.js +139 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -575
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -539
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -261
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +204 -204
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/plugins.js +771 -771
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/sovereign-shield.js +542 -0
- package/server/services/stripe.js +192 -192
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +662 -661
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -0
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/public/score.html +0 -263
- package/server/migrations/006_growth_suite.sql +0 -138
- package/server/routes/growth.js +0 -962
- package/server/services/fairness-engine.js +0 -409
- package/server/services/fairness.js +0 -420
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WAB Safety Shield — Out-of-Band Human Gate (SPEC §8.11)
|
|
5
|
+
*
|
|
6
|
+
* For high-risk actions on Pro+ sites, an agent's request is held in a
|
|
7
|
+
* "pending" state and a one-time confirmation code is dispatched to a
|
|
8
|
+
* channel the agent CANNOT see (Telegram bot, WhatsApp, email, Slack
|
|
9
|
+
* webhook, etc.). Only after a human approves OOB can the agent retry
|
|
10
|
+
* with `confirmation_id` and have the request executed.
|
|
11
|
+
*
|
|
12
|
+
* Design properties:
|
|
13
|
+
* 1. The challenge code is generated server-side; the agent never
|
|
14
|
+
* sees it. Even a fully prompt-injected agent cannot self-approve.
|
|
15
|
+
* 2. Approval is bound to (session, site, action, paramsHash) — drift
|
|
16
|
+
* invalidates the approval (mirrors dry-run binding).
|
|
17
|
+
* 3. Approvals are single-use.
|
|
18
|
+
* 4. Pending challenges expire (default 10 min, max 30 min).
|
|
19
|
+
*
|
|
20
|
+
* This module is transport-agnostic. Transports are registered via
|
|
21
|
+
* `setTransport(name, fn)`; sites pick one in `humanGate.transport`.
|
|
22
|
+
* The default `null` transport is a no-op (used in tests + offline mode)
|
|
23
|
+
* and the challenge code is also returned via an internal admin API
|
|
24
|
+
* (`GET /api/wab/human-gate/:id/peek`) gated by an admin token, so
|
|
25
|
+
* operators can recover from a misconfigured channel.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const crypto = require('crypto');
|
|
29
|
+
const { isDestructiveAction } = require('./token-scope');
|
|
30
|
+
|
|
31
|
+
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
32
|
+
const MAX_TTL_MS = 30 * 60 * 1000;
|
|
33
|
+
const STORE_MAX = 5000;
|
|
34
|
+
|
|
35
|
+
const STATUS = Object.freeze({
|
|
36
|
+
PENDING: 'pending',
|
|
37
|
+
APPROVED: 'approved',
|
|
38
|
+
REJECTED: 'rejected',
|
|
39
|
+
CONSUMED: 'consumed',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const _store = new Map(); // challenge_id -> entry
|
|
43
|
+
const _transports = new Map();
|
|
44
|
+
|
|
45
|
+
// ─── helpers ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function _hashParams(params) {
|
|
48
|
+
const canon = JSON.stringify(_canonicalize(params || {}));
|
|
49
|
+
return crypto.createHash('sha256').update(canon).digest('hex').slice(0, 24);
|
|
50
|
+
}
|
|
51
|
+
function _canonicalize(value) {
|
|
52
|
+
if (value === null || typeof value !== 'object') return value;
|
|
53
|
+
if (Array.isArray(value)) return value.map(_canonicalize);
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const k of Object.keys(value).sort()) out[k] = _canonicalize(value[k]);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function _fingerprint(token) {
|
|
59
|
+
if (!token) return null;
|
|
60
|
+
return crypto.createHash('sha256').update(String(token)).digest('hex').slice(0, 16);
|
|
61
|
+
}
|
|
62
|
+
function _genCode() {
|
|
63
|
+
// 6-digit numeric. Avoids confusing chars on phones.
|
|
64
|
+
return String(crypto.randomInt(100000, 1000000));
|
|
65
|
+
}
|
|
66
|
+
function _genId() {
|
|
67
|
+
return 'wabh_' + crypto.randomBytes(16).toString('hex');
|
|
68
|
+
}
|
|
69
|
+
function _evictIfFull() {
|
|
70
|
+
if (_store.size <= STORE_MAX) return;
|
|
71
|
+
const drop = Math.ceil(STORE_MAX * 0.1);
|
|
72
|
+
let i = 0;
|
|
73
|
+
for (const k of _store.keys()) {
|
|
74
|
+
if (i++ >= drop) break;
|
|
75
|
+
_store.delete(k);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── transports ──────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function setTransport(name, fn) {
|
|
82
|
+
if (typeof fn !== 'function') throw new TypeError('transport must be a function');
|
|
83
|
+
_transports.set(String(name).toLowerCase(), fn);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function _resolveTransport(siteConfig = {}) {
|
|
87
|
+
const name = String(siteConfig?.humanGate?.transport || 'null').toLowerCase();
|
|
88
|
+
return _transports.get(name) || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Default null transport — no-op; useful for offline/dev/tests.
|
|
92
|
+
setTransport('null', async () => ({ ok: true, channel: 'null' }));
|
|
93
|
+
|
|
94
|
+
// ─── policy ──────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether the named action requires an OOB human gate.
|
|
98
|
+
* Trigger conditions:
|
|
99
|
+
* - site.humanGate.enabled === true AND
|
|
100
|
+
* - tier ∈ {pro, premium, enterprise} OR site.humanGate.force === true
|
|
101
|
+
* - action is destructive OR appears in site.humanGate.actions[]
|
|
102
|
+
*/
|
|
103
|
+
function requiresHumanGate(actionName, siteConfig = {}, tier = 'free') {
|
|
104
|
+
const cfg = siteConfig?.humanGate || {};
|
|
105
|
+
if (!cfg.enabled) return false;
|
|
106
|
+
const tierOk = ['pro', 'premium', 'enterprise'].includes(String(tier).toLowerCase()) || cfg.force === true;
|
|
107
|
+
if (!tierOk) return false;
|
|
108
|
+
if (Array.isArray(cfg.actions) && cfg.actions.includes(actionName)) return true;
|
|
109
|
+
// Default: gate destructive verbs (reuse classification from token-scope).
|
|
110
|
+
return isDestructiveAction(actionName, siteConfig);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── challenge lifecycle ─────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a challenge and dispatch it OOB.
|
|
117
|
+
* Returns { challenge_id, expires_at, dispatched_to, status:'pending' }.
|
|
118
|
+
* The plaintext code is NEVER returned to the caller path of the agent;
|
|
119
|
+
* it is delivered only via the configured transport (or visible to the
|
|
120
|
+
* site operator via the admin peek endpoint).
|
|
121
|
+
*/
|
|
122
|
+
async function issueChallenge(ctx, opts = {}) {
|
|
123
|
+
const ttl = Math.min(Math.max(opts.ttlMs || DEFAULT_TTL_MS, 1000), MAX_TTL_MS);
|
|
124
|
+
const id = _genId();
|
|
125
|
+
const code = _genCode();
|
|
126
|
+
const codeHash = crypto.createHash('sha256').update(code).digest('hex');
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
const entry = {
|
|
129
|
+
id,
|
|
130
|
+
status: STATUS.PENDING,
|
|
131
|
+
code_hash: codeHash,
|
|
132
|
+
code_preview_for_admin: code, // used by admin peek only
|
|
133
|
+
session_fingerprint: _fingerprint(ctx.sessionToken),
|
|
134
|
+
site_id: ctx.siteId,
|
|
135
|
+
actor_id: ctx.actorId || null,
|
|
136
|
+
action_name: ctx.actionName,
|
|
137
|
+
params_hash: _hashParams(ctx.params),
|
|
138
|
+
created_at: now,
|
|
139
|
+
expires_at: now + ttl,
|
|
140
|
+
attempts: 0,
|
|
141
|
+
rejected_reason: null,
|
|
142
|
+
};
|
|
143
|
+
_store.set(id, entry);
|
|
144
|
+
_evictIfFull();
|
|
145
|
+
|
|
146
|
+
let dispatchResult = { ok: true, channel: 'none' };
|
|
147
|
+
const transport = opts.transport || _resolveTransport(opts.siteConfig);
|
|
148
|
+
if (transport) {
|
|
149
|
+
try {
|
|
150
|
+
dispatchResult = await transport({
|
|
151
|
+
challenge_id: id,
|
|
152
|
+
code,
|
|
153
|
+
site_id: ctx.siteId,
|
|
154
|
+
action_name: ctx.actionName,
|
|
155
|
+
actor_id: ctx.actorId || null,
|
|
156
|
+
expires_at: new Date(entry.expires_at).toISOString(),
|
|
157
|
+
siteConfig: opts.siteConfig || {},
|
|
158
|
+
}) || { ok: true, channel: 'unknown' };
|
|
159
|
+
} catch (err) {
|
|
160
|
+
dispatchResult = { ok: false, channel: 'error', error: err.message };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
challenge_id: id,
|
|
166
|
+
status: entry.status,
|
|
167
|
+
expires_at: new Date(entry.expires_at).toISOString(),
|
|
168
|
+
dispatched_to: dispatchResult.channel || null,
|
|
169
|
+
dispatch_ok: dispatchResult.ok !== false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Approve a challenge using its OOB code (called by the human's webhook
|
|
175
|
+
* receiver or the operator UI). Rate-limited per challenge to 5 attempts.
|
|
176
|
+
*/
|
|
177
|
+
function approveChallenge(challengeId, code) {
|
|
178
|
+
const entry = _store.get(challengeId);
|
|
179
|
+
if (!entry) return { ok: false, code: 'HUMAN_GATE_NOT_FOUND' };
|
|
180
|
+
if (Date.now() > entry.expires_at) {
|
|
181
|
+
_store.delete(challengeId);
|
|
182
|
+
return { ok: false, code: 'HUMAN_GATE_EXPIRED' };
|
|
183
|
+
}
|
|
184
|
+
if (entry.status !== STATUS.PENDING) {
|
|
185
|
+
return { ok: false, code: 'HUMAN_GATE_BAD_STATE', message: `cannot approve: ${entry.status}` };
|
|
186
|
+
}
|
|
187
|
+
entry.attempts += 1;
|
|
188
|
+
if (entry.attempts > 5) {
|
|
189
|
+
entry.status = STATUS.REJECTED;
|
|
190
|
+
entry.rejected_reason = 'too_many_attempts';
|
|
191
|
+
return { ok: false, code: 'HUMAN_GATE_LOCKED' };
|
|
192
|
+
}
|
|
193
|
+
const supplied = crypto.createHash('sha256').update(String(code || '')).digest('hex');
|
|
194
|
+
let match = false;
|
|
195
|
+
try {
|
|
196
|
+
match = crypto.timingSafeEqual(Buffer.from(supplied, 'hex'), Buffer.from(entry.code_hash, 'hex'));
|
|
197
|
+
} catch { match = false; }
|
|
198
|
+
if (!match) return { ok: false, code: 'HUMAN_GATE_BAD_CODE' };
|
|
199
|
+
entry.status = STATUS.APPROVED;
|
|
200
|
+
entry.approved_at = Date.now();
|
|
201
|
+
return { ok: true, status: entry.status };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function rejectChallenge(challengeId, reason) {
|
|
205
|
+
const entry = _store.get(challengeId);
|
|
206
|
+
if (!entry) return { ok: false, code: 'HUMAN_GATE_NOT_FOUND' };
|
|
207
|
+
if (entry.status !== STATUS.PENDING) {
|
|
208
|
+
return { ok: false, code: 'HUMAN_GATE_BAD_STATE' };
|
|
209
|
+
}
|
|
210
|
+
entry.status = STATUS.REJECTED;
|
|
211
|
+
entry.rejected_reason = String(reason || 'rejected_by_human').slice(0, 200);
|
|
212
|
+
return { ok: true, status: entry.status };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Consume an APPROVED challenge during the agent's retry. Validates
|
|
217
|
+
* binding to the original (session, site, action, params). Single-use.
|
|
218
|
+
*/
|
|
219
|
+
function consumeApproved(challengeId, ctx) {
|
|
220
|
+
if (!challengeId) return { ok: false, code: 'HUMAN_GATE_REQUIRED' };
|
|
221
|
+
const entry = _store.get(challengeId);
|
|
222
|
+
if (!entry) return { ok: false, code: 'HUMAN_GATE_NOT_FOUND' };
|
|
223
|
+
if (Date.now() > entry.expires_at) {
|
|
224
|
+
_store.delete(challengeId);
|
|
225
|
+
return { ok: false, code: 'HUMAN_GATE_EXPIRED' };
|
|
226
|
+
}
|
|
227
|
+
if (entry.status === STATUS.PENDING) return { ok: false, code: 'HUMAN_GATE_PENDING' };
|
|
228
|
+
if (entry.status === STATUS.REJECTED) return { ok: false, code: 'HUMAN_GATE_REJECTED', message: entry.rejected_reason || 'rejected' };
|
|
229
|
+
if (entry.status === STATUS.CONSUMED) return { ok: false, code: 'HUMAN_GATE_CONSUMED' };
|
|
230
|
+
if (entry.status !== STATUS.APPROVED) return { ok: false, code: 'HUMAN_GATE_BAD_STATE' };
|
|
231
|
+
// Binding checks
|
|
232
|
+
if (entry.session_fingerprint !== _fingerprint(ctx.sessionToken)) {
|
|
233
|
+
return { ok: false, code: 'HUMAN_GATE_MISMATCH', message: 'approval issued to a different session' };
|
|
234
|
+
}
|
|
235
|
+
if (entry.site_id !== ctx.siteId) {
|
|
236
|
+
return { ok: false, code: 'HUMAN_GATE_MISMATCH', message: 'approval issued for a different site' };
|
|
237
|
+
}
|
|
238
|
+
if (entry.action_name !== ctx.actionName) {
|
|
239
|
+
return { ok: false, code: 'HUMAN_GATE_MISMATCH', message: 'approval issued for a different action' };
|
|
240
|
+
}
|
|
241
|
+
if (entry.params_hash !== _hashParams(ctx.params)) {
|
|
242
|
+
return { ok: false, code: 'HUMAN_GATE_MISMATCH', message: 'parameters changed since approval' };
|
|
243
|
+
}
|
|
244
|
+
entry.status = STATUS.CONSUMED;
|
|
245
|
+
entry.consumed_at = Date.now();
|
|
246
|
+
return { ok: true, entry };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getStatus(challengeId) {
|
|
250
|
+
const entry = _store.get(challengeId);
|
|
251
|
+
if (!entry) return null;
|
|
252
|
+
return {
|
|
253
|
+
challenge_id: entry.id,
|
|
254
|
+
status: entry.status,
|
|
255
|
+
expires_at: new Date(entry.expires_at).toISOString(),
|
|
256
|
+
rejected_reason: entry.rejected_reason || undefined,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _resetForTests() { _store.clear(); }
|
|
261
|
+
function _peekForAdmin(challengeId) {
|
|
262
|
+
const e = _store.get(challengeId);
|
|
263
|
+
return e ? { ...e } : null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
module.exports = {
|
|
267
|
+
STATUS,
|
|
268
|
+
setTransport,
|
|
269
|
+
requiresHumanGate,
|
|
270
|
+
issueChallenge,
|
|
271
|
+
approveChallenge,
|
|
272
|
+
rejectChallenge,
|
|
273
|
+
consumeApproved,
|
|
274
|
+
getStatus,
|
|
275
|
+
// test/admin helpers
|
|
276
|
+
_resetForTests,
|
|
277
|
+
_peekForAdmin,
|
|
278
|
+
_hashParams,
|
|
279
|
+
DEFAULT_TTL_MS,
|
|
280
|
+
MAX_TTL_MS,
|
|
281
|
+
};
|