paygate-mcp 3.7.0 → 3.9.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/README.md +113 -0
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/gate.d.ts +4 -0
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +25 -6
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +275 -9
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/webhook-router.d.ts +89 -0
- package/dist/webhook-router.d.ts.map +1 -0
- package/dist/webhook-router.js +249 -0
- package/dist/webhook-router.js.map +1 -0
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -183,7 +183,7 @@ class PayGateServer {
|
|
|
183
183
|
this.audit.log('key.auto_topped_up', 'system', `Auto-topup: added ${amount} credits`, {
|
|
184
184
|
keyMasked, creditsAdded: amount, newBalance,
|
|
185
185
|
});
|
|
186
|
-
this.
|
|
186
|
+
this.emitWebhookAdmin('key.auto_topped_up', 'system', {
|
|
187
187
|
keyMasked, creditsAdded: amount, newBalance,
|
|
188
188
|
});
|
|
189
189
|
// Sync to Redis (if configured)
|
|
@@ -333,6 +333,8 @@ class PayGateServer {
|
|
|
333
333
|
return this.handleSetAutoTopup(req, res);
|
|
334
334
|
case '/topup':
|
|
335
335
|
return this.handleTopUp(req, res);
|
|
336
|
+
case '/keys/transfer':
|
|
337
|
+
return this.handleCreditTransfer(req, res);
|
|
336
338
|
case '/balance':
|
|
337
339
|
return this.handleBalance(req, res);
|
|
338
340
|
case '/limits':
|
|
@@ -373,6 +375,16 @@ class PayGateServer {
|
|
|
373
375
|
break;
|
|
374
376
|
case '/webhooks/stats':
|
|
375
377
|
return this.handleWebhookStats(req, res);
|
|
378
|
+
case '/webhooks/filters':
|
|
379
|
+
if (req.method === 'GET')
|
|
380
|
+
return this.handleListWebhookFilters(req, res);
|
|
381
|
+
if (req.method === 'POST')
|
|
382
|
+
return this.handleCreateWebhookFilter(req, res);
|
|
383
|
+
break;
|
|
384
|
+
case '/webhooks/filters/update':
|
|
385
|
+
return this.handleUpdateWebhookFilter(req, res);
|
|
386
|
+
case '/webhooks/filters/delete':
|
|
387
|
+
return this.handleDeleteWebhookFilter(req, res);
|
|
376
388
|
// ─── Team management endpoints ────────────────────────────────────
|
|
377
389
|
case '/teams':
|
|
378
390
|
if (req.method === 'GET')
|
|
@@ -611,7 +623,7 @@ class PayGateServer {
|
|
|
611
623
|
const fired = this.alerts.check(apiKey, keyRecord, { rateLimitDenied: isRateLimited });
|
|
612
624
|
// Send alert events via webhook
|
|
613
625
|
for (const alert of fired) {
|
|
614
|
-
this.
|
|
626
|
+
this.emitWebhookAdmin('alert.fired', 'system', {
|
|
615
627
|
alertType: alert.type,
|
|
616
628
|
keyPrefix: alert.keyPrefix,
|
|
617
629
|
keyName: alert.keyName,
|
|
@@ -808,6 +820,7 @@ class PayGateServer {
|
|
|
808
820
|
setAcl: 'POST /keys/acl — Set tool ACL (requires X-Admin-Key)',
|
|
809
821
|
setExpiry: 'POST /keys/expiry — Set key expiry (requires X-Admin-Key)',
|
|
810
822
|
topUp: 'POST /topup — Add credits (requires X-Admin-Key)',
|
|
823
|
+
transfer: 'POST /keys/transfer — Transfer credits between keys (requires X-Admin-Key)',
|
|
811
824
|
usage: 'GET /usage — Export usage data (requires X-Admin-Key)',
|
|
812
825
|
limits: 'POST /limits — Set spending limit (requires X-Admin-Key)',
|
|
813
826
|
setQuota: 'POST /keys/quota — Set usage quota (requires X-Admin-Key)',
|
|
@@ -825,6 +838,9 @@ class PayGateServer {
|
|
|
825
838
|
alerts: 'GET /alerts — Get pending alerts + POST /alerts — Configure alert rules (requires X-Admin-Key)',
|
|
826
839
|
webhookDeadLetters: 'GET /webhooks/dead-letter — View failed webhook deliveries + DELETE to clear (requires X-Admin-Key)',
|
|
827
840
|
webhookStats: 'GET /webhooks/stats — Webhook delivery statistics (requires X-Admin-Key)',
|
|
841
|
+
webhookFilters: 'GET|POST /webhooks/filters — List or create webhook filter rules (requires X-Admin-Key)',
|
|
842
|
+
updateWebhookFilter: 'POST /webhooks/filters/update — Update a webhook filter rule (requires X-Admin-Key)',
|
|
843
|
+
deleteWebhookFilter: 'POST /webhooks/filters/delete — Delete a webhook filter rule (requires X-Admin-Key)',
|
|
828
844
|
teams: 'GET /teams — List teams + POST /teams — Create team (requires X-Admin-Key)',
|
|
829
845
|
teamsUpdate: 'POST /teams/update — Update team (requires X-Admin-Key)',
|
|
830
846
|
teamsDelete: 'POST /teams/delete — Delete team (requires X-Admin-Key)',
|
|
@@ -964,7 +980,7 @@ class PayGateServer {
|
|
|
964
980
|
deniedTools: record.deniedTools,
|
|
965
981
|
expiresAt: record.expiresAt,
|
|
966
982
|
});
|
|
967
|
-
this.
|
|
983
|
+
this.emitWebhookAdmin('key.created', 'admin', {
|
|
968
984
|
keyMasked: (0, audit_1.maskKeyForAudit)(record.key), name, credits,
|
|
969
985
|
});
|
|
970
986
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -1040,12 +1056,113 @@ class PayGateServer {
|
|
|
1040
1056
|
creditsAdded: credits,
|
|
1041
1057
|
newBalance: record?.credits,
|
|
1042
1058
|
});
|
|
1043
|
-
this.
|
|
1059
|
+
this.emitWebhookAdmin('key.topup', 'admin', {
|
|
1044
1060
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key), creditsAdded: credits, newBalance: record?.credits,
|
|
1045
1061
|
});
|
|
1046
1062
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1047
1063
|
res.end(JSON.stringify({ credits: record?.credits, message: `Added ${credits} credits` }));
|
|
1048
1064
|
}
|
|
1065
|
+
// ─── /keys/transfer — Transfer credits between keys ─────────────────────
|
|
1066
|
+
async handleCreditTransfer(req, res) {
|
|
1067
|
+
if (req.method !== 'POST') {
|
|
1068
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
1069
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1073
|
+
return;
|
|
1074
|
+
const body = await this.readBody(req);
|
|
1075
|
+
let params;
|
|
1076
|
+
try {
|
|
1077
|
+
params = JSON.parse(body);
|
|
1078
|
+
}
|
|
1079
|
+
catch {
|
|
1080
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1081
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (!params.from || !params.to) {
|
|
1085
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1086
|
+
res.end(JSON.stringify({ error: 'Missing "from" and "to" API keys' }));
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
if (params.from === params.to) {
|
|
1090
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1091
|
+
res.end(JSON.stringify({ error: 'Cannot transfer credits to the same key' }));
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const credits = Math.floor(Number(params.credits));
|
|
1095
|
+
if (!Number.isFinite(credits) || credits <= 0) {
|
|
1096
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1097
|
+
res.end(JSON.stringify({ error: 'Credits must be a positive integer' }));
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
// Validate source key exists and has enough credits
|
|
1101
|
+
const sourceRecord = this.gate.store.getKey(params.from);
|
|
1102
|
+
if (!sourceRecord) {
|
|
1103
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1104
|
+
res.end(JSON.stringify({ error: 'Source key not found' }));
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (sourceRecord.credits < credits) {
|
|
1108
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1109
|
+
res.end(JSON.stringify({
|
|
1110
|
+
error: `Insufficient credits: source has ${sourceRecord.credits}, need ${credits}`,
|
|
1111
|
+
}));
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
// Validate destination key exists (getKey returns null for revoked/expired keys)
|
|
1115
|
+
const destRecord = this.gate.store.getKey(params.to);
|
|
1116
|
+
if (!destRecord) {
|
|
1117
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1118
|
+
res.end(JSON.stringify({ error: 'Destination key not found' }));
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
// Perform transfer atomically (deduct from source, add to destination)
|
|
1122
|
+
if (this.redisSync) {
|
|
1123
|
+
// Redis atomic transfer: deduct first, then add
|
|
1124
|
+
const deducted = await this.redisSync.atomicDeduct(params.from, credits);
|
|
1125
|
+
if (!deducted) {
|
|
1126
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1127
|
+
res.end(JSON.stringify({ error: 'Redis deduction failed (insufficient credits or key not found)' }));
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
await this.redisSync.atomicTopup(params.to, credits);
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
// Local store: deduct and add
|
|
1134
|
+
sourceRecord.credits -= credits;
|
|
1135
|
+
destRecord.credits += credits;
|
|
1136
|
+
this.gate.store.save();
|
|
1137
|
+
}
|
|
1138
|
+
const fromBalance = this.gate.store.getKey(params.from)?.credits ?? 0;
|
|
1139
|
+
const toBalance = this.gate.store.getKey(params.to)?.credits ?? 0;
|
|
1140
|
+
const memo = params.memo || '';
|
|
1141
|
+
this.audit.log('key.credits_transferred', 'admin', `Transferred ${credits} credits`, {
|
|
1142
|
+
fromKeyMasked: (0, audit_1.maskKeyForAudit)(params.from),
|
|
1143
|
+
toKeyMasked: (0, audit_1.maskKeyForAudit)(params.to),
|
|
1144
|
+
credits,
|
|
1145
|
+
fromBalance,
|
|
1146
|
+
toBalance,
|
|
1147
|
+
memo,
|
|
1148
|
+
});
|
|
1149
|
+
this.emitWebhookAdmin('key.credits_transferred', 'admin', {
|
|
1150
|
+
fromKeyMasked: (0, audit_1.maskKeyForAudit)(params.from),
|
|
1151
|
+
toKeyMasked: (0, audit_1.maskKeyForAudit)(params.to),
|
|
1152
|
+
credits,
|
|
1153
|
+
fromBalance,
|
|
1154
|
+
toBalance,
|
|
1155
|
+
memo,
|
|
1156
|
+
});
|
|
1157
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1158
|
+
res.end(JSON.stringify({
|
|
1159
|
+
transferred: credits,
|
|
1160
|
+
from: { keyMasked: (0, audit_1.maskKeyForAudit)(params.from), balance: fromBalance },
|
|
1161
|
+
to: { keyMasked: (0, audit_1.maskKeyForAudit)(params.to), balance: toBalance },
|
|
1162
|
+
memo: memo || undefined,
|
|
1163
|
+
message: `Transferred ${credits} credits`,
|
|
1164
|
+
}));
|
|
1165
|
+
}
|
|
1049
1166
|
// ─── /keys/revoke — Revoke a key ──────────────────────────────────────────
|
|
1050
1167
|
async handleRevokeKey(req, res) {
|
|
1051
1168
|
if (req.method !== 'POST') {
|
|
@@ -1086,7 +1203,7 @@ class PayGateServer {
|
|
|
1086
1203
|
this.audit.log('key.revoked', 'admin', `Key revoked`, {
|
|
1087
1204
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1088
1205
|
});
|
|
1089
|
-
this.
|
|
1206
|
+
this.emitWebhookAdmin('key.revoked', 'admin', {
|
|
1090
1207
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1091
1208
|
});
|
|
1092
1209
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1132,7 +1249,7 @@ class PayGateServer {
|
|
|
1132
1249
|
oldKeyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1133
1250
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(rotated.key),
|
|
1134
1251
|
});
|
|
1135
|
-
this.
|
|
1252
|
+
this.emitWebhookAdmin('key.rotated', 'admin', {
|
|
1136
1253
|
oldKeyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1137
1254
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(rotated.key),
|
|
1138
1255
|
});
|
|
@@ -1479,7 +1596,7 @@ class PayGateServer {
|
|
|
1479
1596
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1480
1597
|
threshold, amount, maxDaily,
|
|
1481
1598
|
});
|
|
1482
|
-
this.
|
|
1599
|
+
this.emitWebhookAdmin('key.auto_topup_configured', 'admin', {
|
|
1483
1600
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key), threshold, amount, maxDaily,
|
|
1484
1601
|
});
|
|
1485
1602
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2084,13 +2201,151 @@ class PayGateServer {
|
|
|
2084
2201
|
return;
|
|
2085
2202
|
}
|
|
2086
2203
|
const stats = this.gate.webhook.getRetryStats();
|
|
2204
|
+
const routerStats = this.gate.webhookRouter ? this.gate.webhookRouter.getAggregateStats() : null;
|
|
2087
2205
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2088
2206
|
res.end(JSON.stringify({
|
|
2089
2207
|
configured: true,
|
|
2090
2208
|
maxRetries: this.gate.webhook.maxRetries,
|
|
2091
2209
|
...stats,
|
|
2210
|
+
...(routerStats ? { filters: routerStats } : {}),
|
|
2092
2211
|
}));
|
|
2093
2212
|
}
|
|
2213
|
+
// ─── /webhooks/filters — Webhook filter CRUD ─────────────────────────────
|
|
2214
|
+
handleListWebhookFilters(req, res) {
|
|
2215
|
+
if (!this.checkAdmin(req, res))
|
|
2216
|
+
return;
|
|
2217
|
+
if (!this.gate.webhookRouter) {
|
|
2218
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2219
|
+
res.end(JSON.stringify({ count: 0, filters: [], message: 'No webhook router configured' }));
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
const filters = this.gate.webhookRouter.listRules();
|
|
2223
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2224
|
+
res.end(JSON.stringify({ count: filters.length, filters }));
|
|
2225
|
+
}
|
|
2226
|
+
async handleCreateWebhookFilter(req, res) {
|
|
2227
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2228
|
+
return;
|
|
2229
|
+
if (!this.gate.webhookRouter) {
|
|
2230
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2231
|
+
res.end(JSON.stringify({ error: 'No webhook configured. Set webhookUrl or webhookFilters to enable webhook routing.' }));
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
const body = await this.readBody(req);
|
|
2235
|
+
let params;
|
|
2236
|
+
try {
|
|
2237
|
+
params = JSON.parse(body);
|
|
2238
|
+
}
|
|
2239
|
+
catch {
|
|
2240
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2241
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
try {
|
|
2245
|
+
const rule = this.gate.webhookRouter.addRule({
|
|
2246
|
+
id: '', // auto-generated
|
|
2247
|
+
name: String(params.name || ''),
|
|
2248
|
+
events: Array.isArray(params.events) ? params.events.map(String) : [],
|
|
2249
|
+
url: String(params.url || ''),
|
|
2250
|
+
secret: params.secret ? String(params.secret) : undefined,
|
|
2251
|
+
keyPrefixes: Array.isArray(params.keyPrefixes) ? params.keyPrefixes.map(String) : undefined,
|
|
2252
|
+
active: params.active !== false,
|
|
2253
|
+
});
|
|
2254
|
+
this.audit.log('webhook_filter.created', 'admin', `Webhook filter created: ${rule.name}`, { filterId: rule.id, name: rule.name });
|
|
2255
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
2256
|
+
res.end(JSON.stringify(rule));
|
|
2257
|
+
}
|
|
2258
|
+
catch (err) {
|
|
2259
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2260
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
async handleUpdateWebhookFilter(req, res) {
|
|
2264
|
+
if (req.method !== 'POST') {
|
|
2265
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2266
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2270
|
+
return;
|
|
2271
|
+
if (!this.gate.webhookRouter) {
|
|
2272
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2273
|
+
res.end(JSON.stringify({ error: 'No webhook router configured' }));
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const body = await this.readBody(req);
|
|
2277
|
+
let params;
|
|
2278
|
+
try {
|
|
2279
|
+
params = JSON.parse(body);
|
|
2280
|
+
}
|
|
2281
|
+
catch {
|
|
2282
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2283
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
const filterId = String(params.id || '');
|
|
2287
|
+
if (!filterId) {
|
|
2288
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2289
|
+
res.end(JSON.stringify({ error: 'Missing id field' }));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
try {
|
|
2293
|
+
const rule = this.gate.webhookRouter.updateRule(filterId, {
|
|
2294
|
+
name: params.name !== undefined ? String(params.name) : undefined,
|
|
2295
|
+
events: Array.isArray(params.events) ? params.events.map(String) : undefined,
|
|
2296
|
+
url: params.url !== undefined ? String(params.url) : undefined,
|
|
2297
|
+
secret: params.secret !== undefined ? String(params.secret) : undefined,
|
|
2298
|
+
keyPrefixes: Array.isArray(params.keyPrefixes) ? params.keyPrefixes.map(String) : undefined,
|
|
2299
|
+
active: params.active !== undefined ? Boolean(params.active) : undefined,
|
|
2300
|
+
});
|
|
2301
|
+
this.audit.log('webhook_filter.updated', 'admin', `Webhook filter updated: ${rule.name}`, { filterId: rule.id });
|
|
2302
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2303
|
+
res.end(JSON.stringify(rule));
|
|
2304
|
+
}
|
|
2305
|
+
catch (err) {
|
|
2306
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2307
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async handleDeleteWebhookFilter(req, res) {
|
|
2311
|
+
if (req.method !== 'POST') {
|
|
2312
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2313
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2317
|
+
return;
|
|
2318
|
+
if (!this.gate.webhookRouter) {
|
|
2319
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2320
|
+
res.end(JSON.stringify({ error: 'No webhook router configured' }));
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const body = await this.readBody(req);
|
|
2324
|
+
let params;
|
|
2325
|
+
try {
|
|
2326
|
+
params = JSON.parse(body);
|
|
2327
|
+
}
|
|
2328
|
+
catch {
|
|
2329
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2330
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
const filterId = String(params.id || '');
|
|
2334
|
+
if (!filterId) {
|
|
2335
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2336
|
+
res.end(JSON.stringify({ error: 'Missing id field' }));
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
const deleted = this.gate.webhookRouter.deleteRule(filterId);
|
|
2340
|
+
if (!deleted) {
|
|
2341
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2342
|
+
res.end(JSON.stringify({ error: 'Filter not found' }));
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
this.audit.log('webhook_filter.deleted', 'admin', `Webhook filter deleted: ${filterId}`, { filterId });
|
|
2346
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2347
|
+
res.end(JSON.stringify({ ok: true, message: `Filter ${filterId} deleted` }));
|
|
2348
|
+
}
|
|
2094
2349
|
// ─── /audit — Query audit log ─────────────────────────────────────────────
|
|
2095
2350
|
handleAudit(req, res) {
|
|
2096
2351
|
if (req.method !== 'GET') {
|
|
@@ -2881,7 +3136,7 @@ class PayGateServer {
|
|
|
2881
3136
|
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2882
3137
|
role,
|
|
2883
3138
|
});
|
|
2884
|
-
this.
|
|
3139
|
+
this.emitWebhookAdmin('admin_key.created', callerMasked, {
|
|
2885
3140
|
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2886
3141
|
name: params.name,
|
|
2887
3142
|
role,
|
|
@@ -2935,7 +3190,7 @@ class PayGateServer {
|
|
|
2935
3190
|
this.audit.log('admin_key.revoked', callerMasked, `Revoked admin key ${targetMasked}`, {
|
|
2936
3191
|
revokedKeyMasked: targetMasked,
|
|
2937
3192
|
});
|
|
2938
|
-
this.
|
|
3193
|
+
this.emitWebhookAdmin('admin_key.revoked', callerMasked, {
|
|
2939
3194
|
revokedKeyMasked: targetMasked,
|
|
2940
3195
|
});
|
|
2941
3196
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2946,6 +3201,17 @@ class PayGateServer {
|
|
|
2946
3201
|
* (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
|
|
2947
3202
|
* Fire-and-forget: errors logged, never thrown.
|
|
2948
3203
|
*/
|
|
3204
|
+
/**
|
|
3205
|
+
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
3206
|
+
*/
|
|
3207
|
+
emitWebhookAdmin(type, actor, metadata = {}) {
|
|
3208
|
+
if (this.gate.webhookRouter) {
|
|
3209
|
+
this.gate.webhookRouter.emitAdmin(type, actor, metadata);
|
|
3210
|
+
}
|
|
3211
|
+
else if (this.gate.webhook) {
|
|
3212
|
+
this.gate.webhook.emitAdmin(type, actor, metadata);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
2949
3215
|
syncKeyMutation(apiKey) {
|
|
2950
3216
|
if (!this.redisSync)
|
|
2951
3217
|
return;
|