paygate-mcp 4.8.0 → 5.0.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 +66 -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/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +176 -46
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +25 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +84 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook.d.ts +26 -0
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +47 -0
- package/dist/webhook.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -364,6 +364,8 @@ class PayGateServer {
|
|
|
364
364
|
return this.handleResumeKey(req, res);
|
|
365
365
|
case '/keys/clone':
|
|
366
366
|
return this.handleCloneKey(req, res);
|
|
367
|
+
case '/keys/alias':
|
|
368
|
+
return this.handleSetAlias(req, res);
|
|
367
369
|
case '/keys/rotate':
|
|
368
370
|
return this.handleRotateKey(req, res);
|
|
369
371
|
case '/keys/acl':
|
|
@@ -436,6 +438,10 @@ class PayGateServer {
|
|
|
436
438
|
return this.handleWebhookStats(req, res);
|
|
437
439
|
case '/webhooks/log':
|
|
438
440
|
return this.handleWebhookLog(req, res);
|
|
441
|
+
case '/webhooks/pause':
|
|
442
|
+
return this.handleWebhookPause(req, res);
|
|
443
|
+
case '/webhooks/resume':
|
|
444
|
+
return this.handleWebhookResume(req, res);
|
|
439
445
|
case '/webhooks/test':
|
|
440
446
|
return this.handleWebhookTest(req, res);
|
|
441
447
|
case '/webhooks/filters':
|
|
@@ -885,6 +891,7 @@ class PayGateServer {
|
|
|
885
891
|
suspendKey: 'POST /keys/suspend — Temporarily suspend a key (requires X-Admin-Key)',
|
|
886
892
|
resumeKey: 'POST /keys/resume — Resume a suspended key (requires X-Admin-Key)',
|
|
887
893
|
cloneKey: 'POST /keys/clone — Clone a key with same config (requires X-Admin-Key)',
|
|
894
|
+
keyAlias: 'POST /keys/alias — Set or clear a human-readable alias for a key (requires X-Admin-Key)',
|
|
888
895
|
rotateKey: 'POST /keys/rotate — Rotate a key (requires X-Admin-Key)',
|
|
889
896
|
setAcl: 'POST /keys/acl — Set tool ACL (requires X-Admin-Key)',
|
|
890
897
|
setExpiry: 'POST /keys/expiry — Set key expiry (requires X-Admin-Key)',
|
|
@@ -914,6 +921,8 @@ class PayGateServer {
|
|
|
914
921
|
webhookTest: 'POST /webhooks/test — Send test event to webhook URL and return result (requires X-Admin-Key)',
|
|
915
922
|
webhookStats: 'GET /webhooks/stats — Webhook delivery statistics (requires X-Admin-Key)',
|
|
916
923
|
webhookLog: 'GET /webhooks/log — Webhook delivery log with status, timing, and filters (requires X-Admin-Key)',
|
|
924
|
+
webhookPause: 'POST /webhooks/pause — Pause webhook delivery (events buffered) (requires X-Admin-Key)',
|
|
925
|
+
webhookResume: 'POST /webhooks/resume — Resume webhook delivery and flush buffered events (requires X-Admin-Key)',
|
|
917
926
|
webhookFilters: 'GET|POST /webhooks/filters — List or create webhook filter rules (requires X-Admin-Key)',
|
|
918
927
|
updateWebhookFilter: 'POST /webhooks/filters/update — Update a webhook filter rule (requires X-Admin-Key)',
|
|
919
928
|
deleteWebhookFilter: 'POST /webhooks/filters/delete — Delete a webhook filter rule (requires X-Admin-Key)',
|
|
@@ -1114,20 +1123,23 @@ class PayGateServer {
|
|
|
1114
1123
|
res.end(JSON.stringify({ error: 'Credits must be a positive integer' }));
|
|
1115
1124
|
return;
|
|
1116
1125
|
}
|
|
1126
|
+
// Resolve alias to actual key
|
|
1127
|
+
const resolved = this.gate.store.resolveKey(params.key);
|
|
1128
|
+
const actualKey = resolved ? resolved.key : params.key;
|
|
1117
1129
|
// Use Redis atomic topup when available, fall back to local store
|
|
1118
1130
|
let success;
|
|
1119
1131
|
if (this.redisSync) {
|
|
1120
|
-
success = await this.redisSync.atomicTopup(
|
|
1132
|
+
success = await this.redisSync.atomicTopup(actualKey, credits);
|
|
1121
1133
|
}
|
|
1122
1134
|
else {
|
|
1123
|
-
success = this.gate.store.addCredits(
|
|
1135
|
+
success = this.gate.store.addCredits(actualKey, credits);
|
|
1124
1136
|
}
|
|
1125
1137
|
if (!success) {
|
|
1126
1138
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1127
1139
|
res.end(JSON.stringify({ error: 'Key not found or inactive' }));
|
|
1128
1140
|
return;
|
|
1129
1141
|
}
|
|
1130
|
-
const record = this.gate.store.getKey(
|
|
1142
|
+
const record = this.gate.store.getKey(actualKey);
|
|
1131
1143
|
this.audit.log('key.topup', 'admin', `Added ${credits} credits`, {
|
|
1132
1144
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1133
1145
|
creditsAdded: credits,
|
|
@@ -1175,7 +1187,7 @@ class PayGateServer {
|
|
|
1175
1187
|
return;
|
|
1176
1188
|
}
|
|
1177
1189
|
// Validate source key exists and has enough credits
|
|
1178
|
-
const sourceRecord = this.gate.store.
|
|
1190
|
+
const sourceRecord = this.gate.store.resolveKey(params.from);
|
|
1179
1191
|
if (!sourceRecord) {
|
|
1180
1192
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1181
1193
|
res.end(JSON.stringify({ error: 'Source key not found' }));
|
|
@@ -1189,7 +1201,7 @@ class PayGateServer {
|
|
|
1189
1201
|
return;
|
|
1190
1202
|
}
|
|
1191
1203
|
// Validate destination key exists (getKey returns null for revoked/expired keys)
|
|
1192
|
-
const destRecord = this.gate.store.
|
|
1204
|
+
const destRecord = this.gate.store.resolveKey(params.to);
|
|
1193
1205
|
if (!destRecord) {
|
|
1194
1206
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1195
1207
|
res.end(JSON.stringify({ error: 'Destination key not found' }));
|
|
@@ -1198,13 +1210,13 @@ class PayGateServer {
|
|
|
1198
1210
|
// Perform transfer atomically (deduct from source, add to destination)
|
|
1199
1211
|
if (this.redisSync) {
|
|
1200
1212
|
// Redis atomic transfer: deduct first, then add
|
|
1201
|
-
const deducted = await this.redisSync.atomicDeduct(
|
|
1213
|
+
const deducted = await this.redisSync.atomicDeduct(sourceRecord.key, credits);
|
|
1202
1214
|
if (!deducted) {
|
|
1203
1215
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1204
1216
|
res.end(JSON.stringify({ error: 'Redis deduction failed (insufficient credits or key not found)' }));
|
|
1205
1217
|
return;
|
|
1206
1218
|
}
|
|
1207
|
-
await this.redisSync.atomicTopup(
|
|
1219
|
+
await this.redisSync.atomicTopup(destRecord.key, credits);
|
|
1208
1220
|
}
|
|
1209
1221
|
else {
|
|
1210
1222
|
// Local store: deduct and add
|
|
@@ -1212,20 +1224,20 @@ class PayGateServer {
|
|
|
1212
1224
|
destRecord.credits += credits;
|
|
1213
1225
|
this.gate.store.save();
|
|
1214
1226
|
}
|
|
1215
|
-
const fromBalance =
|
|
1216
|
-
const toBalance =
|
|
1227
|
+
const fromBalance = sourceRecord.credits;
|
|
1228
|
+
const toBalance = destRecord.credits;
|
|
1217
1229
|
const memo = params.memo || '';
|
|
1218
1230
|
this.audit.log('key.credits_transferred', 'admin', `Transferred ${credits} credits`, {
|
|
1219
|
-
fromKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1220
|
-
toKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1231
|
+
fromKeyMasked: (0, audit_1.maskKeyForAudit)(sourceRecord.key),
|
|
1232
|
+
toKeyMasked: (0, audit_1.maskKeyForAudit)(destRecord.key),
|
|
1221
1233
|
credits,
|
|
1222
1234
|
fromBalance,
|
|
1223
1235
|
toBalance,
|
|
1224
1236
|
memo,
|
|
1225
1237
|
});
|
|
1226
1238
|
this.emitWebhookAdmin('key.credits_transferred', 'admin', {
|
|
1227
|
-
fromKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1228
|
-
toKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1239
|
+
fromKeyMasked: (0, audit_1.maskKeyForAudit)(sourceRecord.key),
|
|
1240
|
+
toKeyMasked: (0, audit_1.maskKeyForAudit)(destRecord.key),
|
|
1229
1241
|
credits,
|
|
1230
1242
|
fromBalance,
|
|
1231
1243
|
toBalance,
|
|
@@ -1234,8 +1246,8 @@ class PayGateServer {
|
|
|
1234
1246
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1235
1247
|
res.end(JSON.stringify({
|
|
1236
1248
|
transferred: credits,
|
|
1237
|
-
from: { keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1238
|
-
to: { keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1249
|
+
from: { keyMasked: (0, audit_1.maskKeyForAudit)(sourceRecord.key), balance: fromBalance, credits: fromBalance },
|
|
1250
|
+
to: { keyMasked: (0, audit_1.maskKeyForAudit)(destRecord.key), balance: toBalance, credits: toBalance },
|
|
1239
1251
|
memo: memo || undefined,
|
|
1240
1252
|
message: `Transferred ${credits} credits`,
|
|
1241
1253
|
}));
|
|
@@ -1509,13 +1521,16 @@ class PayGateServer {
|
|
|
1509
1521
|
res.end(JSON.stringify({ error: 'Missing key' }));
|
|
1510
1522
|
return;
|
|
1511
1523
|
}
|
|
1524
|
+
// Resolve alias to actual key
|
|
1525
|
+
const resolved = this.gate.store.resolveKeyRaw(params.key);
|
|
1526
|
+
const actualKey = resolved ? resolved.key : params.key;
|
|
1512
1527
|
// Use Redis-backed revoke when available (broadcasts to other instances)
|
|
1513
1528
|
let success;
|
|
1514
1529
|
if (this.redisSync) {
|
|
1515
|
-
success = await this.redisSync.revokeKey(
|
|
1530
|
+
success = await this.redisSync.revokeKey(actualKey);
|
|
1516
1531
|
}
|
|
1517
1532
|
else {
|
|
1518
|
-
success = this.gate.store.revokeKey(
|
|
1533
|
+
success = this.gate.store.revokeKey(actualKey);
|
|
1519
1534
|
}
|
|
1520
1535
|
if (!success) {
|
|
1521
1536
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
@@ -1523,13 +1538,13 @@ class PayGateServer {
|
|
|
1523
1538
|
return;
|
|
1524
1539
|
}
|
|
1525
1540
|
this.audit.log('key.revoked', 'admin', `Key revoked`, {
|
|
1526
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1541
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(actualKey),
|
|
1527
1542
|
});
|
|
1528
1543
|
this.emitWebhookAdmin('key.revoked', 'admin', {
|
|
1529
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1544
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(actualKey),
|
|
1530
1545
|
});
|
|
1531
1546
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1532
|
-
res.end(JSON.stringify({ message: 'Key revoked' }));
|
|
1547
|
+
res.end(JSON.stringify({ message: 'Key revoked', revoked: true }));
|
|
1533
1548
|
}
|
|
1534
1549
|
// ─── /keys/suspend — Temporarily suspend a key ─────────────────────────────
|
|
1535
1550
|
async handleSuspendKey(req, res) {
|
|
@@ -1555,7 +1570,7 @@ class PayGateServer {
|
|
|
1555
1570
|
res.end(JSON.stringify({ error: 'Missing key' }));
|
|
1556
1571
|
return;
|
|
1557
1572
|
}
|
|
1558
|
-
const record = this.gate.store.
|
|
1573
|
+
const record = this.gate.store.resolveKeyRaw(params.key);
|
|
1559
1574
|
if (!record) {
|
|
1560
1575
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1561
1576
|
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
@@ -1571,19 +1586,19 @@ class PayGateServer {
|
|
|
1571
1586
|
res.end(JSON.stringify({ error: 'Key is already suspended' }));
|
|
1572
1587
|
return;
|
|
1573
1588
|
}
|
|
1574
|
-
const success = this.gate.store.suspendKey(
|
|
1589
|
+
const success = this.gate.store.suspendKey(record.key);
|
|
1575
1590
|
if (!success) {
|
|
1576
1591
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1577
1592
|
res.end(JSON.stringify({ error: 'Failed to suspend key' }));
|
|
1578
1593
|
return;
|
|
1579
1594
|
}
|
|
1580
|
-
this.syncKeyMutation(
|
|
1595
|
+
this.syncKeyMutation(record.key);
|
|
1581
1596
|
this.audit.log('key.suspended', 'admin', `Key suspended${params.reason ? ': ' + params.reason : ''}`, {
|
|
1582
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1597
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(record.key),
|
|
1583
1598
|
reason: params.reason || null,
|
|
1584
1599
|
});
|
|
1585
1600
|
this.emitWebhookAdmin('key.suspended', 'admin', {
|
|
1586
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1601
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(record.key),
|
|
1587
1602
|
reason: params.reason || null,
|
|
1588
1603
|
});
|
|
1589
1604
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1613,7 +1628,7 @@ class PayGateServer {
|
|
|
1613
1628
|
res.end(JSON.stringify({ error: 'Missing key' }));
|
|
1614
1629
|
return;
|
|
1615
1630
|
}
|
|
1616
|
-
const record = this.gate.store.
|
|
1631
|
+
const record = this.gate.store.resolveKeyRaw(params.key);
|
|
1617
1632
|
if (!record) {
|
|
1618
1633
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1619
1634
|
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
@@ -1629,18 +1644,18 @@ class PayGateServer {
|
|
|
1629
1644
|
res.end(JSON.stringify({ error: 'Key is not suspended' }));
|
|
1630
1645
|
return;
|
|
1631
1646
|
}
|
|
1632
|
-
const success = this.gate.store.resumeKey(
|
|
1647
|
+
const success = this.gate.store.resumeKey(record.key);
|
|
1633
1648
|
if (!success) {
|
|
1634
1649
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1635
1650
|
res.end(JSON.stringify({ error: 'Failed to resume key' }));
|
|
1636
1651
|
return;
|
|
1637
1652
|
}
|
|
1638
|
-
this.syncKeyMutation(
|
|
1653
|
+
this.syncKeyMutation(record.key);
|
|
1639
1654
|
this.audit.log('key.resumed', 'admin', 'Key resumed', {
|
|
1640
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1655
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(record.key),
|
|
1641
1656
|
});
|
|
1642
1657
|
this.emitWebhookAdmin('key.resumed', 'admin', {
|
|
1643
|
-
keyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1658
|
+
keyMasked: (0, audit_1.maskKeyForAudit)(record.key),
|
|
1644
1659
|
});
|
|
1645
1660
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1646
1661
|
res.end(JSON.stringify({ message: 'Key resumed', suspended: false }));
|
|
@@ -1670,7 +1685,7 @@ class PayGateServer {
|
|
|
1670
1685
|
return;
|
|
1671
1686
|
}
|
|
1672
1687
|
// Use getKeyRaw to allow cloning suspended/expired keys (but not revoked)
|
|
1673
|
-
const source = this.gate.store.
|
|
1688
|
+
const source = this.gate.store.resolveKeyRaw(params.key);
|
|
1674
1689
|
if (!source) {
|
|
1675
1690
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1676
1691
|
res.end(JSON.stringify({ error: 'Source key not found' }));
|
|
@@ -1681,7 +1696,7 @@ class PayGateServer {
|
|
|
1681
1696
|
res.end(JSON.stringify({ error: 'Cannot clone a revoked key' }));
|
|
1682
1697
|
return;
|
|
1683
1698
|
}
|
|
1684
|
-
const cloned = this.gate.store.cloneKey(
|
|
1699
|
+
const cloned = this.gate.store.cloneKey(source.key, {
|
|
1685
1700
|
name: params.name,
|
|
1686
1701
|
credits: params.credits,
|
|
1687
1702
|
tags: params.tags,
|
|
@@ -1694,14 +1709,14 @@ class PayGateServer {
|
|
|
1694
1709
|
}
|
|
1695
1710
|
// Sync new key to Redis
|
|
1696
1711
|
this.syncKeyMutation(cloned.key);
|
|
1697
|
-
this.audit.log('key.cloned', 'admin', `Key cloned from ${(0, audit_1.maskKeyForAudit)(
|
|
1698
|
-
sourceKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1712
|
+
this.audit.log('key.cloned', 'admin', `Key cloned from ${(0, audit_1.maskKeyForAudit)(source.key)}`, {
|
|
1713
|
+
sourceKeyMasked: (0, audit_1.maskKeyForAudit)(source.key),
|
|
1699
1714
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(cloned.key),
|
|
1700
1715
|
name: cloned.name,
|
|
1701
1716
|
credits: cloned.credits,
|
|
1702
1717
|
});
|
|
1703
1718
|
this.emitWebhookAdmin('key.cloned', 'admin', {
|
|
1704
|
-
sourceKeyMasked: (0, audit_1.maskKeyForAudit)(
|
|
1719
|
+
sourceKeyMasked: (0, audit_1.maskKeyForAudit)(source.key),
|
|
1705
1720
|
newKeyMasked: (0, audit_1.maskKeyForAudit)(cloned.key),
|
|
1706
1721
|
name: cloned.name,
|
|
1707
1722
|
credits: cloned.credits,
|
|
@@ -1712,6 +1727,7 @@ class PayGateServer {
|
|
|
1712
1727
|
key: cloned.key,
|
|
1713
1728
|
name: cloned.name,
|
|
1714
1729
|
credits: cloned.credits,
|
|
1730
|
+
clonedFrom: source.key.slice(0, 10) + '...',
|
|
1715
1731
|
sourceName: source.name,
|
|
1716
1732
|
allowedTools: cloned.allowedTools,
|
|
1717
1733
|
deniedTools: cloned.deniedTools,
|
|
@@ -1724,6 +1740,65 @@ class PayGateServer {
|
|
|
1724
1740
|
spendingLimit: cloned.spendingLimit,
|
|
1725
1741
|
}));
|
|
1726
1742
|
}
|
|
1743
|
+
// ─── /keys/alias — Set or clear key alias ──────────────────────────────────
|
|
1744
|
+
async handleSetAlias(req, res) {
|
|
1745
|
+
if (req.method !== 'POST') {
|
|
1746
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
1747
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
if (!this.checkAdmin(req, res))
|
|
1751
|
+
return;
|
|
1752
|
+
const raw = await this.readBody(req);
|
|
1753
|
+
const params = JSON.parse(raw);
|
|
1754
|
+
if (!params.key) {
|
|
1755
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1756
|
+
res.end(JSON.stringify({ error: 'Missing "key" parameter' }));
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
// Resolve the key (support existing aliases for the source key)
|
|
1760
|
+
const record = this.gate.store.resolveKeyRaw(params.key);
|
|
1761
|
+
if (!record) {
|
|
1762
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1763
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const alias = params.alias !== undefined ? (params.alias === null || params.alias === '' ? null : String(params.alias)) : undefined;
|
|
1767
|
+
if (alias === undefined) {
|
|
1768
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1769
|
+
res.end(JSON.stringify({ error: 'Missing "alias" parameter (string to set, null to clear)' }));
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
const result = this.gate.store.setAlias(record.key, alias);
|
|
1773
|
+
if (!result.success) {
|
|
1774
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1775
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
const action = alias ? `set to "${alias}"` : 'cleared';
|
|
1779
|
+
this.audit.log('key.alias_set', 'admin', `Key alias ${action} for ${record.key.slice(0, 10)}...`, {
|
|
1780
|
+
key: record.key.slice(0, 10),
|
|
1781
|
+
alias: alias || null,
|
|
1782
|
+
});
|
|
1783
|
+
// Sync to Redis if configured
|
|
1784
|
+
if (typeof this.syncKeyMutation === 'function') {
|
|
1785
|
+
this.syncKeyMutation(record.key);
|
|
1786
|
+
}
|
|
1787
|
+
// Webhook event
|
|
1788
|
+
if (this.gate.webhook) {
|
|
1789
|
+
this.gate.webhook.emitAdmin('key.created', 'admin', {
|
|
1790
|
+
key: record.key.slice(0, 10),
|
|
1791
|
+
alias: alias || null,
|
|
1792
|
+
event: 'alias_set',
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1796
|
+
res.end(JSON.stringify({
|
|
1797
|
+
key: record.key.slice(0, 10) + '...',
|
|
1798
|
+
alias: record.alias || null,
|
|
1799
|
+
message: `Alias ${action}`,
|
|
1800
|
+
}));
|
|
1801
|
+
}
|
|
1727
1802
|
// ─── /keys/rotate — Rotate API key ─────────────────────────────────────────
|
|
1728
1803
|
async handleRotateKey(req, res) {
|
|
1729
1804
|
if (req.method !== 'POST') {
|
|
@@ -1807,7 +1882,7 @@ class PayGateServer {
|
|
|
1807
1882
|
return;
|
|
1808
1883
|
}
|
|
1809
1884
|
this.syncKeyMutation(params.key);
|
|
1810
|
-
const record = this.gate.store.
|
|
1885
|
+
const record = this.gate.store.resolveKey(params.key);
|
|
1811
1886
|
this.audit.log('key.acl_updated', 'admin', `ACL updated`, {
|
|
1812
1887
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1813
1888
|
allowedTools: record?.allowedTools || [],
|
|
@@ -1859,7 +1934,7 @@ class PayGateServer {
|
|
|
1859
1934
|
return;
|
|
1860
1935
|
}
|
|
1861
1936
|
this.syncKeyMutation(params.key);
|
|
1862
|
-
const record = this.gate.store.
|
|
1937
|
+
const record = this.gate.store.resolveKeyRaw(params.key);
|
|
1863
1938
|
this.audit.log('key.expiry_updated', 'admin', expiresAt ? `Key expiry set to ${expiresAt}` : 'Key expiry removed', {
|
|
1864
1939
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1865
1940
|
expiresAt: record?.expiresAt || null,
|
|
@@ -1962,7 +2037,7 @@ class PayGateServer {
|
|
|
1962
2037
|
return;
|
|
1963
2038
|
}
|
|
1964
2039
|
this.syncKeyMutation(params.key);
|
|
1965
|
-
const record = this.gate.store.
|
|
2040
|
+
const record = this.gate.store.resolveKey(params.key);
|
|
1966
2041
|
this.audit.log('key.tags_updated', 'admin', `Tags updated`, {
|
|
1967
2042
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
1968
2043
|
tags: record?.tags || {},
|
|
@@ -2009,7 +2084,7 @@ class PayGateServer {
|
|
|
2009
2084
|
return;
|
|
2010
2085
|
}
|
|
2011
2086
|
this.syncKeyMutation(params.key);
|
|
2012
|
-
const record = this.gate.store.
|
|
2087
|
+
const record = this.gate.store.resolveKey(params.key);
|
|
2013
2088
|
this.audit.log('key.ip_updated', 'admin', `IP allowlist updated`, {
|
|
2014
2089
|
keyMasked: (0, audit_1.maskKeyForAudit)(params.key),
|
|
2015
2090
|
ipAllowlist: record?.ipAllowlist || [],
|
|
@@ -2066,14 +2141,14 @@ class PayGateServer {
|
|
|
2066
2141
|
res.end(JSON.stringify({ error: 'Missing key query parameter' }));
|
|
2067
2142
|
return;
|
|
2068
2143
|
}
|
|
2069
|
-
// Verify key exists (use
|
|
2070
|
-
const record = this.gate.store.
|
|
2144
|
+
// Verify key exists (use resolveKeyRaw to allow querying by alias and expired/suspended keys)
|
|
2145
|
+
const record = this.gate.store.resolveKeyRaw(key);
|
|
2071
2146
|
if (!record) {
|
|
2072
2147
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2073
2148
|
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
2074
2149
|
return;
|
|
2075
2150
|
}
|
|
2076
|
-
const usage = this.gate.meter.getKeyUsage(key, since);
|
|
2151
|
+
const usage = this.gate.meter.getKeyUsage(record.key, since);
|
|
2077
2152
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2078
2153
|
res.end(JSON.stringify({
|
|
2079
2154
|
key: key.slice(0, 10) + '...',
|
|
@@ -2109,7 +2184,7 @@ class PayGateServer {
|
|
|
2109
2184
|
res.end(JSON.stringify({ error: 'Missing key' }));
|
|
2110
2185
|
return;
|
|
2111
2186
|
}
|
|
2112
|
-
const record = this.gate.store.
|
|
2187
|
+
const record = this.gate.store.resolveKey(params.key);
|
|
2113
2188
|
if (!record) {
|
|
2114
2189
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2115
2190
|
res.end(JSON.stringify({ error: 'Key not found or inactive' }));
|
|
@@ -2230,7 +2305,7 @@ class PayGateServer {
|
|
|
2230
2305
|
res.end(JSON.stringify({ error: 'Missing key' }));
|
|
2231
2306
|
return;
|
|
2232
2307
|
}
|
|
2233
|
-
const record = this.gate.store.
|
|
2308
|
+
const record = this.gate.store.resolveKey(params.key);
|
|
2234
2309
|
if (!record) {
|
|
2235
2310
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2236
2311
|
res.end(JSON.stringify({ error: 'Key not found or inactive' }));
|
|
@@ -2983,6 +3058,61 @@ class PayGateServer {
|
|
|
2983
3058
|
entries,
|
|
2984
3059
|
}));
|
|
2985
3060
|
}
|
|
3061
|
+
// ─── /webhooks/pause — Pause webhook delivery ─────────────────────────────
|
|
3062
|
+
handleWebhookPause(req, res) {
|
|
3063
|
+
if (req.method !== 'POST') {
|
|
3064
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
3065
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
if (!this.checkAdmin(req, res))
|
|
3069
|
+
return;
|
|
3070
|
+
if (!this.gate.webhook) {
|
|
3071
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3072
|
+
res.end(JSON.stringify({ error: 'No webhook configured' }));
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
const paused = this.gate.webhook.pause();
|
|
3076
|
+
if (!paused) {
|
|
3077
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3078
|
+
res.end(JSON.stringify({ paused: true, message: 'Already paused' }));
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
this.audit.log('webhook.pause', 'admin', 'Webhook delivery paused');
|
|
3082
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3083
|
+
res.end(JSON.stringify({
|
|
3084
|
+
paused: true,
|
|
3085
|
+
message: 'Webhook delivery paused. Events will be buffered until resumed.',
|
|
3086
|
+
}));
|
|
3087
|
+
}
|
|
3088
|
+
// ─── /webhooks/resume — Resume webhook delivery ───────────────────────────
|
|
3089
|
+
handleWebhookResume(req, res) {
|
|
3090
|
+
if (req.method !== 'POST') {
|
|
3091
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
3092
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
if (!this.checkAdmin(req, res))
|
|
3096
|
+
return;
|
|
3097
|
+
if (!this.gate.webhook) {
|
|
3098
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
3099
|
+
res.end(JSON.stringify({ error: 'No webhook configured' }));
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
const result = this.gate.webhook.resume();
|
|
3103
|
+
if (!result.resumed) {
|
|
3104
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3105
|
+
res.end(JSON.stringify({ paused: false, message: 'Not paused' }));
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
this.audit.log('webhook.resume', 'admin', `Webhook delivery resumed, ${result.flushedEvents} buffered events flushed`);
|
|
3109
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3110
|
+
res.end(JSON.stringify({
|
|
3111
|
+
paused: false,
|
|
3112
|
+
message: 'Webhook delivery resumed.',
|
|
3113
|
+
flushedEvents: result.flushedEvents,
|
|
3114
|
+
}));
|
|
3115
|
+
}
|
|
2986
3116
|
// ─── /webhooks/test — Send test event ────────────────────────────────────
|
|
2987
3117
|
async handleWebhookTest(req, res) {
|
|
2988
3118
|
if (req.method !== 'POST') {
|
|
@@ -3478,7 +3608,7 @@ class PayGateServer {
|
|
|
3478
3608
|
return;
|
|
3479
3609
|
}
|
|
3480
3610
|
// Verify the key exists
|
|
3481
|
-
const keyRecord = this.gate.store.
|
|
3611
|
+
const keyRecord = this.gate.store.resolveKey(params.key);
|
|
3482
3612
|
if (!keyRecord) {
|
|
3483
3613
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
3484
3614
|
res.end(JSON.stringify({ error: 'API key not found' }));
|
|
@@ -3597,7 +3727,7 @@ class PayGateServer {
|
|
|
3597
3727
|
return;
|
|
3598
3728
|
}
|
|
3599
3729
|
// Verify the parent key exists and is active
|
|
3600
|
-
const keyRecord = this.gate.store.
|
|
3730
|
+
const keyRecord = this.gate.store.resolveKey(params.key);
|
|
3601
3731
|
if (!keyRecord || !keyRecord.active) {
|
|
3602
3732
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
3603
3733
|
res.end(JSON.stringify({ error: 'API key not found or inactive' }));
|