paygate-mcp 3.6.0 → 3.8.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 +88 -2
- 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/groups.d.ts +7 -0
- package/dist/groups.d.ts.map +1 -1
- package/dist/groups.js +26 -0
- package/dist/groups.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 +8 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +178 -10
- 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/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -159,6 +159,10 @@ export declare class PayGateServer {
|
|
|
159
159
|
private handleGetDeadLetters;
|
|
160
160
|
private handleClearDeadLetters;
|
|
161
161
|
private handleWebhookStats;
|
|
162
|
+
private handleListWebhookFilters;
|
|
163
|
+
private handleCreateWebhookFilter;
|
|
164
|
+
private handleUpdateWebhookFilter;
|
|
165
|
+
private handleDeleteWebhookFilter;
|
|
162
166
|
private handleAudit;
|
|
163
167
|
private handleAuditExport;
|
|
164
168
|
private handleAuditStats;
|
|
@@ -189,6 +193,10 @@ export declare class PayGateServer {
|
|
|
189
193
|
* (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
|
|
190
194
|
* Fire-and-forget: errors logged, never thrown.
|
|
191
195
|
*/
|
|
196
|
+
/**
|
|
197
|
+
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
198
|
+
*/
|
|
199
|
+
private emitWebhookAdmin;
|
|
192
200
|
private syncKeyMutation;
|
|
193
201
|
private readBody;
|
|
194
202
|
stop(): Promise<void>;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAE,aAAa,EAAkB,mBAAmB,EAAkB,MAAM,SAAS,CAAC;AAS7F,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,cAAc,EAAqD,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAmB,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAS,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,eAAe,EAA6B,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,aAAa,EAAqB,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
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)
|
|
@@ -194,7 +194,8 @@ class PayGateServer {
|
|
|
194
194
|
// Plugin manager for extensible middleware hooks
|
|
195
195
|
this.plugins = new plugin_1.PluginManager();
|
|
196
196
|
this.gate.pluginManager = this.plugins;
|
|
197
|
-
|
|
197
|
+
const groupsStatePath = statePath ? statePath.replace(/\.json$/, '-groups.json') : undefined;
|
|
198
|
+
this.groups = new groups_1.KeyGroupManager(groupsStatePath);
|
|
198
199
|
this.gate.groupManager = this.groups;
|
|
199
200
|
this.metrics.registerGauge('paygate_plugins_total', 'Number of registered plugins', () => {
|
|
200
201
|
return this.plugins.count;
|
|
@@ -372,6 +373,16 @@ class PayGateServer {
|
|
|
372
373
|
break;
|
|
373
374
|
case '/webhooks/stats':
|
|
374
375
|
return this.handleWebhookStats(req, res);
|
|
376
|
+
case '/webhooks/filters':
|
|
377
|
+
if (req.method === 'GET')
|
|
378
|
+
return this.handleListWebhookFilters(req, res);
|
|
379
|
+
if (req.method === 'POST')
|
|
380
|
+
return this.handleCreateWebhookFilter(req, res);
|
|
381
|
+
break;
|
|
382
|
+
case '/webhooks/filters/update':
|
|
383
|
+
return this.handleUpdateWebhookFilter(req, res);
|
|
384
|
+
case '/webhooks/filters/delete':
|
|
385
|
+
return this.handleDeleteWebhookFilter(req, res);
|
|
375
386
|
// ─── Team management endpoints ────────────────────────────────────
|
|
376
387
|
case '/teams':
|
|
377
388
|
if (req.method === 'GET')
|
|
@@ -610,7 +621,7 @@ class PayGateServer {
|
|
|
610
621
|
const fired = this.alerts.check(apiKey, keyRecord, { rateLimitDenied: isRateLimited });
|
|
611
622
|
// Send alert events via webhook
|
|
612
623
|
for (const alert of fired) {
|
|
613
|
-
this.
|
|
624
|
+
this.emitWebhookAdmin('alert.fired', 'system', {
|
|
614
625
|
alertType: alert.type,
|
|
615
626
|
keyPrefix: alert.keyPrefix,
|
|
616
627
|
keyName: alert.keyName,
|
|
@@ -824,6 +835,9 @@ class PayGateServer {
|
|
|
824
835
|
alerts: 'GET /alerts — Get pending alerts + POST /alerts — Configure alert rules (requires X-Admin-Key)',
|
|
825
836
|
webhookDeadLetters: 'GET /webhooks/dead-letter — View failed webhook deliveries + DELETE to clear (requires X-Admin-Key)',
|
|
826
837
|
webhookStats: 'GET /webhooks/stats — Webhook delivery statistics (requires X-Admin-Key)',
|
|
838
|
+
webhookFilters: 'GET|POST /webhooks/filters — List or create webhook filter rules (requires X-Admin-Key)',
|
|
839
|
+
updateWebhookFilter: 'POST /webhooks/filters/update — Update a webhook filter rule (requires X-Admin-Key)',
|
|
840
|
+
deleteWebhookFilter: 'POST /webhooks/filters/delete — Delete a webhook filter rule (requires X-Admin-Key)',
|
|
827
841
|
teams: 'GET /teams — List teams + POST /teams — Create team (requires X-Admin-Key)',
|
|
828
842
|
teamsUpdate: 'POST /teams/update — Update team (requires X-Admin-Key)',
|
|
829
843
|
teamsDelete: 'POST /teams/delete — Delete team (requires X-Admin-Key)',
|
|
@@ -963,7 +977,7 @@ class PayGateServer {
|
|
|
963
977
|
deniedTools: record.deniedTools,
|
|
964
978
|
expiresAt: record.expiresAt,
|
|
965
979
|
});
|
|
966
|
-
this.
|
|
980
|
+
this.emitWebhookAdmin('key.created', 'admin', {
|
|
967
981
|
keyMasked: (0, audit_1.maskKeyForAudit)(record.key), name, credits,
|
|
968
982
|
});
|
|
969
983
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -1039,7 +1053,7 @@ class PayGateServer {
|
|
|
1039
1053
|
creditsAdded: credits,
|
|
1040
1054
|
newBalance: record?.credits,
|
|
1041
1055
|
});
|
|
1042
|
-
this.
|
|
1056
|
+
this.emitWebhookAdmin('key.topup', 'admin', {
|
|
1043
1057
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key), creditsAdded: credits, newBalance: record?.credits,
|
|
1044
1058
|
});
|
|
1045
1059
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1085,7 +1099,7 @@ class PayGateServer {
|
|
|
1085
1099
|
this.audit.log('key.revoked', 'admin', `Key revoked`, {
|
|
1086
1100
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1087
1101
|
});
|
|
1088
|
-
this.
|
|
1102
|
+
this.emitWebhookAdmin('key.revoked', 'admin', {
|
|
1089
1103
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1090
1104
|
});
|
|
1091
1105
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1131,7 +1145,7 @@ class PayGateServer {
|
|
|
1131
1145
|
oldKeyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1132
1146
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(rotated.key),
|
|
1133
1147
|
});
|
|
1134
|
-
this.
|
|
1148
|
+
this.emitWebhookAdmin('key.rotated', 'admin', {
|
|
1135
1149
|
oldKeyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1136
1150
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(rotated.key),
|
|
1137
1151
|
});
|
|
@@ -1478,7 +1492,7 @@ class PayGateServer {
|
|
|
1478
1492
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1479
1493
|
threshold, amount, maxDaily,
|
|
1480
1494
|
});
|
|
1481
|
-
this.
|
|
1495
|
+
this.emitWebhookAdmin('key.auto_topup_configured', 'admin', {
|
|
1482
1496
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key), threshold, amount, maxDaily,
|
|
1483
1497
|
});
|
|
1484
1498
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2083,13 +2097,151 @@ class PayGateServer {
|
|
|
2083
2097
|
return;
|
|
2084
2098
|
}
|
|
2085
2099
|
const stats = this.gate.webhook.getRetryStats();
|
|
2100
|
+
const routerStats = this.gate.webhookRouter ? this.gate.webhookRouter.getAggregateStats() : null;
|
|
2086
2101
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2087
2102
|
res.end(JSON.stringify({
|
|
2088
2103
|
configured: true,
|
|
2089
2104
|
maxRetries: this.gate.webhook.maxRetries,
|
|
2090
2105
|
...stats,
|
|
2106
|
+
...(routerStats ? { filters: routerStats } : {}),
|
|
2091
2107
|
}));
|
|
2092
2108
|
}
|
|
2109
|
+
// ─── /webhooks/filters — Webhook filter CRUD ─────────────────────────────
|
|
2110
|
+
handleListWebhookFilters(req, res) {
|
|
2111
|
+
if (!this.checkAdmin(req, res))
|
|
2112
|
+
return;
|
|
2113
|
+
if (!this.gate.webhookRouter) {
|
|
2114
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2115
|
+
res.end(JSON.stringify({ count: 0, filters: [], message: 'No webhook router configured' }));
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
const filters = this.gate.webhookRouter.listRules();
|
|
2119
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2120
|
+
res.end(JSON.stringify({ count: filters.length, filters }));
|
|
2121
|
+
}
|
|
2122
|
+
async handleCreateWebhookFilter(req, res) {
|
|
2123
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2124
|
+
return;
|
|
2125
|
+
if (!this.gate.webhookRouter) {
|
|
2126
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2127
|
+
res.end(JSON.stringify({ error: 'No webhook configured. Set webhookUrl or webhookFilters to enable webhook routing.' }));
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
const body = await this.readBody(req);
|
|
2131
|
+
let params;
|
|
2132
|
+
try {
|
|
2133
|
+
params = JSON.parse(body);
|
|
2134
|
+
}
|
|
2135
|
+
catch {
|
|
2136
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2137
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
const rule = this.gate.webhookRouter.addRule({
|
|
2142
|
+
id: '', // auto-generated
|
|
2143
|
+
name: String(params.name || ''),
|
|
2144
|
+
events: Array.isArray(params.events) ? params.events.map(String) : [],
|
|
2145
|
+
url: String(params.url || ''),
|
|
2146
|
+
secret: params.secret ? String(params.secret) : undefined,
|
|
2147
|
+
keyPrefixes: Array.isArray(params.keyPrefixes) ? params.keyPrefixes.map(String) : undefined,
|
|
2148
|
+
active: params.active !== false,
|
|
2149
|
+
});
|
|
2150
|
+
this.audit.log('webhook_filter.created', 'admin', `Webhook filter created: ${rule.name}`, { filterId: rule.id, name: rule.name });
|
|
2151
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
2152
|
+
res.end(JSON.stringify(rule));
|
|
2153
|
+
}
|
|
2154
|
+
catch (err) {
|
|
2155
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2156
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
async handleUpdateWebhookFilter(req, res) {
|
|
2160
|
+
if (req.method !== 'POST') {
|
|
2161
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2162
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2166
|
+
return;
|
|
2167
|
+
if (!this.gate.webhookRouter) {
|
|
2168
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2169
|
+
res.end(JSON.stringify({ error: 'No webhook router configured' }));
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
const body = await this.readBody(req);
|
|
2173
|
+
let params;
|
|
2174
|
+
try {
|
|
2175
|
+
params = JSON.parse(body);
|
|
2176
|
+
}
|
|
2177
|
+
catch {
|
|
2178
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2179
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const filterId = String(params.id || '');
|
|
2183
|
+
if (!filterId) {
|
|
2184
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2185
|
+
res.end(JSON.stringify({ error: 'Missing id field' }));
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
try {
|
|
2189
|
+
const rule = this.gate.webhookRouter.updateRule(filterId, {
|
|
2190
|
+
name: params.name !== undefined ? String(params.name) : undefined,
|
|
2191
|
+
events: Array.isArray(params.events) ? params.events.map(String) : undefined,
|
|
2192
|
+
url: params.url !== undefined ? String(params.url) : undefined,
|
|
2193
|
+
secret: params.secret !== undefined ? String(params.secret) : undefined,
|
|
2194
|
+
keyPrefixes: Array.isArray(params.keyPrefixes) ? params.keyPrefixes.map(String) : undefined,
|
|
2195
|
+
active: params.active !== undefined ? Boolean(params.active) : undefined,
|
|
2196
|
+
});
|
|
2197
|
+
this.audit.log('webhook_filter.updated', 'admin', `Webhook filter updated: ${rule.name}`, { filterId: rule.id });
|
|
2198
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2199
|
+
res.end(JSON.stringify(rule));
|
|
2200
|
+
}
|
|
2201
|
+
catch (err) {
|
|
2202
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2203
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
async handleDeleteWebhookFilter(req, res) {
|
|
2207
|
+
if (req.method !== 'POST') {
|
|
2208
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2209
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2213
|
+
return;
|
|
2214
|
+
if (!this.gate.webhookRouter) {
|
|
2215
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2216
|
+
res.end(JSON.stringify({ error: 'No webhook router configured' }));
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
const body = await this.readBody(req);
|
|
2220
|
+
let params;
|
|
2221
|
+
try {
|
|
2222
|
+
params = JSON.parse(body);
|
|
2223
|
+
}
|
|
2224
|
+
catch {
|
|
2225
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2226
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const filterId = String(params.id || '');
|
|
2230
|
+
if (!filterId) {
|
|
2231
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2232
|
+
res.end(JSON.stringify({ error: 'Missing id field' }));
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const deleted = this.gate.webhookRouter.deleteRule(filterId);
|
|
2236
|
+
if (!deleted) {
|
|
2237
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2238
|
+
res.end(JSON.stringify({ error: 'Filter not found' }));
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
this.audit.log('webhook_filter.deleted', 'admin', `Webhook filter deleted: ${filterId}`, { filterId });
|
|
2242
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2243
|
+
res.end(JSON.stringify({ ok: true, message: `Filter ${filterId} deleted` }));
|
|
2244
|
+
}
|
|
2093
2245
|
// ─── /audit — Query audit log ─────────────────────────────────────────────
|
|
2094
2246
|
handleAudit(req, res) {
|
|
2095
2247
|
if (req.method !== 'GET') {
|
|
@@ -2621,6 +2773,7 @@ class PayGateServer {
|
|
|
2621
2773
|
tags: params.tags,
|
|
2622
2774
|
});
|
|
2623
2775
|
this.audit.log('group.created', 'admin', `Group created: ${group.name}`, { groupId: group.id, name: group.name });
|
|
2776
|
+
this.groups.saveToFile();
|
|
2624
2777
|
if (this.redisSync)
|
|
2625
2778
|
this.redisSync.saveGroup(group).catch(() => { });
|
|
2626
2779
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -2675,6 +2828,7 @@ class PayGateServer {
|
|
|
2675
2828
|
tags: params.tags,
|
|
2676
2829
|
});
|
|
2677
2830
|
this.audit.log('group.updated', 'admin', `Group updated: ${group.name}`, { groupId: group.id });
|
|
2831
|
+
this.groups.saveToFile();
|
|
2678
2832
|
if (this.redisSync)
|
|
2679
2833
|
this.redisSync.saveGroup(group).catch(() => { });
|
|
2680
2834
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2716,6 +2870,7 @@ class PayGateServer {
|
|
|
2716
2870
|
return;
|
|
2717
2871
|
}
|
|
2718
2872
|
this.audit.log('group.deleted', 'admin', `Group deleted: ${groupId}`, { groupId });
|
|
2873
|
+
this.groups.saveToFile();
|
|
2719
2874
|
if (this.redisSync) {
|
|
2720
2875
|
this.redisSync.deleteGroup(groupId).catch(() => { });
|
|
2721
2876
|
this.redisSync.saveGroupAssignments().catch(() => { });
|
|
@@ -2762,6 +2917,7 @@ class PayGateServer {
|
|
|
2762
2917
|
this.audit.log('group.key_assigned', 'admin', `Key assigned to group ${groupId}`, {
|
|
2763
2918
|
keyMasked: (0, audit_1.maskKeyForAudit)(apiKey), groupId,
|
|
2764
2919
|
});
|
|
2920
|
+
this.groups.saveToFile();
|
|
2765
2921
|
if (this.redisSync) {
|
|
2766
2922
|
this.redisSync.saveGroupAssignments().catch(() => { });
|
|
2767
2923
|
this.syncKeyMutation(apiKey);
|
|
@@ -2810,6 +2966,7 @@ class PayGateServer {
|
|
|
2810
2966
|
delete keyRecord.group;
|
|
2811
2967
|
}
|
|
2812
2968
|
this.audit.log('group.key_removed', 'admin', `Key removed from group`, { keyMasked: (0, audit_1.maskKeyForAudit)(apiKey) });
|
|
2969
|
+
this.groups.saveToFile();
|
|
2813
2970
|
if (this.redisSync) {
|
|
2814
2971
|
this.redisSync.saveGroupAssignments().catch(() => { });
|
|
2815
2972
|
this.syncKeyMutation(apiKey);
|
|
@@ -2875,7 +3032,7 @@ class PayGateServer {
|
|
|
2875
3032
|
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2876
3033
|
role,
|
|
2877
3034
|
});
|
|
2878
|
-
this.
|
|
3035
|
+
this.emitWebhookAdmin('admin_key.created', callerMasked, {
|
|
2879
3036
|
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2880
3037
|
name: params.name,
|
|
2881
3038
|
role,
|
|
@@ -2929,7 +3086,7 @@ class PayGateServer {
|
|
|
2929
3086
|
this.audit.log('admin_key.revoked', callerMasked, `Revoked admin key ${targetMasked}`, {
|
|
2930
3087
|
revokedKeyMasked: targetMasked,
|
|
2931
3088
|
});
|
|
2932
|
-
this.
|
|
3089
|
+
this.emitWebhookAdmin('admin_key.revoked', callerMasked, {
|
|
2933
3090
|
revokedKeyMasked: targetMasked,
|
|
2934
3091
|
});
|
|
2935
3092
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2940,6 +3097,17 @@ class PayGateServer {
|
|
|
2940
3097
|
* (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
|
|
2941
3098
|
* Fire-and-forget: errors logged, never thrown.
|
|
2942
3099
|
*/
|
|
3100
|
+
/**
|
|
3101
|
+
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
3102
|
+
*/
|
|
3103
|
+
emitWebhookAdmin(type, actor, metadata = {}) {
|
|
3104
|
+
if (this.gate.webhookRouter) {
|
|
3105
|
+
this.gate.webhookRouter.emitAdmin(type, actor, metadata);
|
|
3106
|
+
}
|
|
3107
|
+
else if (this.gate.webhook) {
|
|
3108
|
+
this.gate.webhook.emitAdmin(type, actor, metadata);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
2943
3111
|
syncKeyMutation(apiKey) {
|
|
2944
3112
|
if (!this.redisSync)
|
|
2945
3113
|
return;
|