paygate-mcp 7.1.0 → 7.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/README.md +73 -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 +13 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +352 -0
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
98
98
|
- **Admin Event Stream** — `GET /admin/events` SSE endpoint streams real-time audit events to admin clients — tool calls, denials, key operations, maintenance changes, all with optional `?types=` filter for event type filtering, keepalive pings, multi-client support
|
|
99
99
|
- **Key Notes** — `POST /keys/notes` adds timestamped notes to API keys, `GET /keys/notes?key=...` lists notes, `DELETE /keys/notes?key=...&index=N` removes notes — max 50 per key, 1000 char limit, works on suspended/revoked keys, alias support, audit trail
|
|
100
100
|
- **Scheduled Actions** — `POST /keys/schedule` creates future-dated actions (revoke/suspend/topup) on API keys, `GET /keys/schedule` lists pending schedules with optional `?key=` filter, `DELETE /keys/schedule?id=...` cancels a schedule — max 20 per key, alias support, background execution timer, audit trail
|
|
101
|
+
- **Key Activity Timeline** — `GET /keys/activity?key=...` returns a unified chronological feed of audit events and usage events for a specific key — newest first, optional `?since=` and `?limit=` filters, alias support
|
|
102
|
+
- **Credit Reservations** — `POST /keys/reserve` holds credits, `POST /keys/reserve/commit` deducts held credits, `POST /keys/reserve/release` frees the hold, `GET /keys/reserve` lists active reservations — prevents overcommit, configurable TTL (10s–1h), max 50 per key, auto-expiry, audit trail
|
|
101
103
|
- **Config Hot Reload** — `POST /config/reload` reloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart
|
|
102
104
|
- **Webhook Events** — POST batched usage events to any URL for external billing/alerting
|
|
103
105
|
- **Config File Mode** — Load all settings from a JSON file (`--config`)
|
|
@@ -2002,6 +2004,77 @@ curl -X DELETE "http://localhost:3402/keys/schedule?id=sched_1" \
|
|
|
2002
2004
|
|
|
2003
2005
|
Supported actions: `revoke`, `suspend`, `topup` (requires `params.credits`). Max 20 schedules per key. Supports aliases. Background timer checks every 10 seconds. All create/execute/cancel operations recorded in audit trail (`schedule.created` / `schedule.executed` / `schedule.cancelled`).
|
|
2004
2006
|
|
|
2007
|
+
### Key Activity Timeline
|
|
2008
|
+
|
|
2009
|
+
Get a unified chronological feed of all events for a specific key — audit events (creation, suspension, notes, etc.) and usage events (tool calls, denials) merged into one timeline:
|
|
2010
|
+
|
|
2011
|
+
```bash
|
|
2012
|
+
# Get activity for a key (newest first, default limit 50)
|
|
2013
|
+
curl "http://localhost:3402/keys/activity?key=pg_..." -H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
2014
|
+
|
|
2015
|
+
# With filters
|
|
2016
|
+
curl "http://localhost:3402/keys/activity?key=pg_...&limit=20&since=2025-03-15T00:00:00Z" \
|
|
2017
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
**Response:**
|
|
2021
|
+
|
|
2022
|
+
```json
|
|
2023
|
+
{
|
|
2024
|
+
"key": "pg_abc1...2345",
|
|
2025
|
+
"name": "my-agent",
|
|
2026
|
+
"total": 42,
|
|
2027
|
+
"limit": 50,
|
|
2028
|
+
"events": [
|
|
2029
|
+
{ "timestamp": "2025-03-16T10:30:00Z", "source": "usage", "type": "tool.call", "message": "Called search (5 credits)", "metadata": { "tool": "search", "creditsCharged": 5, "allowed": true } },
|
|
2030
|
+
{ "timestamp": "2025-03-16T09:00:00Z", "source": "audit", "type": "key.note_added", "message": "Note added to key", "metadata": { "key": "pg_abc1...2345" } },
|
|
2031
|
+
{ "timestamp": "2025-03-15T14:00:00Z", "source": "audit", "type": "key.created", "message": "Key created: my-agent", "metadata": { "keyMasked": "pg_abc1...2345" } }
|
|
2032
|
+
]
|
|
2033
|
+
}
|
|
2034
|
+
```
|
|
2035
|
+
|
|
2036
|
+
Max 200 events per request. Supports aliases. Works on suspended and revoked keys.
|
|
2037
|
+
|
|
2038
|
+
### Credit Reservations
|
|
2039
|
+
|
|
2040
|
+
Pre-reserve credits before executing expensive operations. Prevents overcommit in concurrent scenarios:
|
|
2041
|
+
|
|
2042
|
+
```bash
|
|
2043
|
+
# Reserve 500 credits (hold for 5 min default, or set ttlSeconds)
|
|
2044
|
+
curl -X POST http://localhost:3402/keys/reserve \
|
|
2045
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
|
|
2046
|
+
-d '{"key": "pg_...", "credits": 500, "ttlSeconds": 300, "memo": "Batch job #42"}'
|
|
2047
|
+
|
|
2048
|
+
# Commit — deducts the held credits
|
|
2049
|
+
curl -X POST http://localhost:3402/keys/reserve/commit \
|
|
2050
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
|
|
2051
|
+
-d '{"reservationId": "rsv_1"}'
|
|
2052
|
+
|
|
2053
|
+
# Release — frees the hold without deducting
|
|
2054
|
+
curl -X POST http://localhost:3402/keys/reserve/release \
|
|
2055
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
|
|
2056
|
+
-d '{"reservationId": "rsv_1"}'
|
|
2057
|
+
|
|
2058
|
+
# List active reservations (optional ?key= filter)
|
|
2059
|
+
curl "http://localhost:3402/keys/reserve" -H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
2060
|
+
```
|
|
2061
|
+
|
|
2062
|
+
**Response (reserve):**
|
|
2063
|
+
|
|
2064
|
+
```json
|
|
2065
|
+
{
|
|
2066
|
+
"id": "rsv_1",
|
|
2067
|
+
"key": "pg_abc1...2345",
|
|
2068
|
+
"credits": 500,
|
|
2069
|
+
"createdAt": "2025-03-16T10:30:00Z",
|
|
2070
|
+
"expiresAt": "2025-03-16T10:35:00Z",
|
|
2071
|
+
"memo": "Batch job #42",
|
|
2072
|
+
"available": 500
|
|
2073
|
+
}
|
|
2074
|
+
```
|
|
2075
|
+
|
|
2076
|
+
TTL range: 10s to 1h (default 5 min). Max 50 reservations per key. Expired reservations auto-cleanup. Alias support. Rejects revoked/suspended keys. Audit trail (`credits.reserved` / `credits.committed` / `credits.released`).
|
|
2077
|
+
|
|
2005
2078
|
### IP Allowlisting
|
|
2006
2079
|
|
|
2007
2080
|
Restrict API keys to specific IP addresses or CIDR ranges:
|
package/dist/audit.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* session lifecycle, and admin operations. Ring buffer with configurable
|
|
6
6
|
* max size and age-based retention. Zero external dependencies.
|
|
7
7
|
*/
|
|
8
|
-
export type AuditEventType = 'key.created' | 'key.revoked' | 'key.suspended' | 'key.resumed' | 'key.cloned' | 'key.rotated' | 'key.topup' | 'key.acl_updated' | 'key.expiry_updated' | 'key.quota_updated' | 'key.tags_updated' | 'key.ip_updated' | 'key.limit_updated' | 'gate.allow' | 'gate.deny' | 'session.created' | 'session.destroyed' | 'oauth.client_registered' | 'oauth.token_issued' | 'oauth.token_revoked' | 'team.created' | 'team.updated' | 'team.deleted' | 'team.key_assigned' | 'team.key_removed' | 'admin.auth_failed' | 'admin.alerts_configured' | 'webhook.dead_letter_cleared' | 'webhook.replayed' | 'webhook.test' | 'webhook.pause' | 'webhook.resume' | 'key.alias_set' | 'key.expiry_warning' | 'template.created' | 'template.updated' | 'template.deleted' | 'token.created' | 'token.revoked' | 'billing.refund' | 'key.auto_topup_configured' | 'key.auto_topped_up' | 'admin_key.created' | 'admin_key.revoked' | 'group.created' | 'group.updated' | 'group.deleted' | 'group.key_assigned' | 'group.key_removed' | 'key.credits_transferred' | 'keys.exported' | 'keys.imported' | 'webhook_filter.created' | 'webhook_filter.updated' | 'webhook_filter.deleted' | 'config.reloaded' | 'config.export' | 'maintenance.enabled' | 'maintenance.disabled' | 'key.note_added' | 'key.note_deleted' | 'schedule.created' | 'schedule.executed' | 'schedule.cancelled';
|
|
8
|
+
export type AuditEventType = 'key.created' | 'key.revoked' | 'key.suspended' | 'key.resumed' | 'key.cloned' | 'key.rotated' | 'key.topup' | 'key.acl_updated' | 'key.expiry_updated' | 'key.quota_updated' | 'key.tags_updated' | 'key.ip_updated' | 'key.limit_updated' | 'gate.allow' | 'gate.deny' | 'session.created' | 'session.destroyed' | 'oauth.client_registered' | 'oauth.token_issued' | 'oauth.token_revoked' | 'team.created' | 'team.updated' | 'team.deleted' | 'team.key_assigned' | 'team.key_removed' | 'admin.auth_failed' | 'admin.alerts_configured' | 'webhook.dead_letter_cleared' | 'webhook.replayed' | 'webhook.test' | 'webhook.pause' | 'webhook.resume' | 'key.alias_set' | 'key.expiry_warning' | 'template.created' | 'template.updated' | 'template.deleted' | 'token.created' | 'token.revoked' | 'billing.refund' | 'key.auto_topup_configured' | 'key.auto_topped_up' | 'admin_key.created' | 'admin_key.revoked' | 'group.created' | 'group.updated' | 'group.deleted' | 'group.key_assigned' | 'group.key_removed' | 'key.credits_transferred' | 'keys.exported' | 'keys.imported' | 'webhook_filter.created' | 'webhook_filter.updated' | 'webhook_filter.deleted' | 'config.reloaded' | 'config.export' | 'maintenance.enabled' | 'maintenance.disabled' | 'key.note_added' | 'key.note_deleted' | 'schedule.created' | 'schedule.executed' | 'schedule.cancelled' | 'credits.reserved' | 'credits.committed' | 'credits.released';
|
|
9
9
|
export interface AuditEvent {
|
|
10
10
|
/** Monotonically increasing ID */
|
|
11
11
|
id: number;
|
package/dist/audit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,MAAM,cAAc,GAEtB,aAAa,GACb,aAAa,GACb,eAAe,GACf,aAAa,GACb,YAAY,GACZ,aAAa,GACb,WAAW,GACX,iBAAiB,GACjB,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,GAClB,gBAAgB,GAChB,mBAAmB,GAEnB,YAAY,GACZ,WAAW,GAEX,iBAAiB,GACjB,mBAAmB,GAEnB,yBAAyB,GACzB,oBAAoB,GACpB,qBAAqB,GAErB,cAAc,GACd,cAAc,GACd,cAAc,GACd,mBAAmB,GACnB,kBAAkB,GAElB,mBAAmB,GACnB,yBAAyB,GAEzB,6BAA6B,GAC7B,kBAAkB,GAClB,cAAc,GACd,eAAe,GACf,gBAAgB,GAEhB,eAAe,GAEf,oBAAoB,GAEpB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAElB,eAAe,GACf,eAAe,GAEf,gBAAgB,GAEhB,2BAA2B,GAC3B,oBAAoB,GAEpB,mBAAmB,GACnB,mBAAmB,GAEnB,eAAe,GACf,eAAe,GACf,eAAe,GACf,oBAAoB,GACpB,mBAAmB,GAEnB,yBAAyB,GAEzB,eAAe,GACf,eAAe,GAEf,wBAAwB,GACxB,wBAAwB,GACxB,wBAAwB,GAExB,iBAAiB,GACjB,eAAe,GAEf,qBAAqB,GACrB,sBAAsB,GAEtB,gBAAgB,GAChB,kBAAkB,GAElB,kBAAkB,GAClB,mBAAmB,GACnB,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -113,6 +113,10 @@ export declare class PayGateServer {
|
|
|
113
113
|
private scheduleTimer;
|
|
114
114
|
/** Auto-incrementing schedule ID counter */
|
|
115
115
|
private nextScheduleId;
|
|
116
|
+
/** Credit reservations (holds) */
|
|
117
|
+
private creditReservations;
|
|
118
|
+
/** Auto-incrementing reservation ID counter */
|
|
119
|
+
private nextReservationId;
|
|
116
120
|
/** Number of in-flight /mcp requests */
|
|
117
121
|
private inflight;
|
|
118
122
|
/** Config file path for hot reload (null if not using config file) */
|
|
@@ -239,6 +243,15 @@ export declare class PayGateServer {
|
|
|
239
243
|
private handleCancelSchedule;
|
|
240
244
|
/** Execute any scheduled actions that are due. Called by the schedule timer. */
|
|
241
245
|
private executeScheduledActions;
|
|
246
|
+
private handleKeyActivity;
|
|
247
|
+
private handleListReservations;
|
|
248
|
+
private handleCreateReservation;
|
|
249
|
+
private handleCommitReservation;
|
|
250
|
+
private handleReleaseReservation;
|
|
251
|
+
/** Get total held credits for a key across all active reservations. */
|
|
252
|
+
private getHeldCredits;
|
|
253
|
+
/** Remove expired reservations. */
|
|
254
|
+
private cleanupExpiredReservations;
|
|
242
255
|
private handleConfigReload;
|
|
243
256
|
private handleWebhookStats;
|
|
244
257
|
private handleWebhookLog;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -231,6 +231,10 @@ class PayGateServer {
|
|
|
231
231
|
scheduleTimer = null;
|
|
232
232
|
/** Auto-incrementing schedule ID counter */
|
|
233
233
|
nextScheduleId = 1;
|
|
234
|
+
/** Credit reservations (holds) */
|
|
235
|
+
creditReservations = new Map();
|
|
236
|
+
/** Auto-incrementing reservation ID counter */
|
|
237
|
+
nextReservationId = 1;
|
|
234
238
|
/** Number of in-flight /mcp requests */
|
|
235
239
|
inflight = 0;
|
|
236
240
|
/** Config file path for hot reload (null if not using config file) */
|
|
@@ -568,6 +572,32 @@ class PayGateServer {
|
|
|
568
572
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
569
573
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
570
574
|
return;
|
|
575
|
+
case '/keys/activity':
|
|
576
|
+
if (req.method === 'GET')
|
|
577
|
+
return this.handleKeyActivity(req, res);
|
|
578
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
579
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
|
|
580
|
+
return;
|
|
581
|
+
case '/keys/reserve':
|
|
582
|
+
if (req.method === 'GET')
|
|
583
|
+
return this.handleListReservations(req, res);
|
|
584
|
+
if (req.method === 'POST')
|
|
585
|
+
return this.handleCreateReservation(req, res);
|
|
586
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
587
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
588
|
+
return;
|
|
589
|
+
case '/keys/reserve/commit':
|
|
590
|
+
if (req.method === 'POST')
|
|
591
|
+
return this.handleCommitReservation(req, res);
|
|
592
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
593
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
594
|
+
return;
|
|
595
|
+
case '/keys/reserve/release':
|
|
596
|
+
if (req.method === 'POST')
|
|
597
|
+
return this.handleReleaseReservation(req, res);
|
|
598
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
599
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
600
|
+
return;
|
|
571
601
|
case '/keys/rotate':
|
|
572
602
|
return this.handleRotateKey(req, res);
|
|
573
603
|
case '/keys/acl':
|
|
@@ -1207,6 +1237,8 @@ class PayGateServer {
|
|
|
1207
1237
|
adminEvents: 'GET /admin/events — Real-time SSE stream of server events (requires X-Admin-Key, Accept: text/event-stream)',
|
|
1208
1238
|
keyNotes: 'GET /keys/notes?key=... — List notes + POST to add + DELETE to remove (requires X-Admin-Key)',
|
|
1209
1239
|
keySchedule: 'GET /keys/schedule?key=... — List schedules + POST to create + DELETE to cancel (requires X-Admin-Key)',
|
|
1240
|
+
keyActivity: 'GET /keys/activity?key=... — Unified activity timeline for a key (requires X-Admin-Key)',
|
|
1241
|
+
creditReservations: 'POST /keys/reserve to hold credits, POST /keys/reserve/commit to deduct, POST /keys/reserve/release to release, GET /keys/reserve to list (requires X-Admin-Key)',
|
|
1210
1242
|
...(this.oauth ? {
|
|
1211
1243
|
oauthMetadata: 'GET /.well-known/oauth-authorization-server — OAuth 2.1 server metadata',
|
|
1212
1244
|
oauthRegister: 'POST /oauth/register — Register OAuth client',
|
|
@@ -4293,6 +4325,326 @@ class PayGateServer {
|
|
|
4293
4325
|
}
|
|
4294
4326
|
}
|
|
4295
4327
|
}
|
|
4328
|
+
// ─── /keys/activity — Unified activity timeline for a key ────────────────
|
|
4329
|
+
handleKeyActivity(req, res) {
|
|
4330
|
+
if (!this.checkAdmin(req, res))
|
|
4331
|
+
return;
|
|
4332
|
+
const urlParts = req.url?.split('?') || [];
|
|
4333
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
4334
|
+
const keyParam = params.get('key');
|
|
4335
|
+
if (!keyParam) {
|
|
4336
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4337
|
+
res.end(JSON.stringify({ error: 'Missing required query parameter: key' }));
|
|
4338
|
+
return;
|
|
4339
|
+
}
|
|
4340
|
+
const record = this.gate.store.resolveKeyRaw(keyParam);
|
|
4341
|
+
if (!record) {
|
|
4342
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4343
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
const since = params.get('since') || undefined;
|
|
4347
|
+
const limit = Math.min(200, Math.max(1, parseInt(params.get('limit') || '50', 10) || 50));
|
|
4348
|
+
const maskedKey = (0, audit_1.maskKeyForAudit)(record.key);
|
|
4349
|
+
// 1. Collect audit events for this key
|
|
4350
|
+
// Audit events store masked keys in metadata.key or metadata.keyMasked
|
|
4351
|
+
const auditResult = this.audit.query({
|
|
4352
|
+
since,
|
|
4353
|
+
limit: 1000, // Grab up to 1000 to merge
|
|
4354
|
+
});
|
|
4355
|
+
const keyAuditEvents = auditResult.events.filter(e => {
|
|
4356
|
+
// Check metadata.key or metadata.keyMasked (both patterns are used)
|
|
4357
|
+
for (const field of ['key', 'keyMasked', 'sourceKey', 'destKey']) {
|
|
4358
|
+
const val = e.metadata?.[field];
|
|
4359
|
+
if (val && typeof val === 'string' && val === maskedKey)
|
|
4360
|
+
return true;
|
|
4361
|
+
}
|
|
4362
|
+
// Check actor field (gate events use masked key as actor)
|
|
4363
|
+
if (e.actor === maskedKey)
|
|
4364
|
+
return true;
|
|
4365
|
+
return false;
|
|
4366
|
+
});
|
|
4367
|
+
// 2. Collect usage events for this key
|
|
4368
|
+
const usageEvents = this.gate.meter.getEvents(since).filter(e => e.apiKey === record.key);
|
|
4369
|
+
// 3. Merge into unified timeline
|
|
4370
|
+
const timeline = [];
|
|
4371
|
+
for (const e of keyAuditEvents) {
|
|
4372
|
+
timeline.push({
|
|
4373
|
+
timestamp: e.timestamp,
|
|
4374
|
+
source: 'audit',
|
|
4375
|
+
type: e.type,
|
|
4376
|
+
message: e.message,
|
|
4377
|
+
metadata: e.metadata,
|
|
4378
|
+
});
|
|
4379
|
+
}
|
|
4380
|
+
for (const e of usageEvents) {
|
|
4381
|
+
timeline.push({
|
|
4382
|
+
timestamp: e.timestamp,
|
|
4383
|
+
source: 'usage',
|
|
4384
|
+
type: e.allowed ? 'tool.call' : 'tool.denied',
|
|
4385
|
+
message: e.allowed
|
|
4386
|
+
? `Called ${e.tool} (${e.creditsCharged} credits)`
|
|
4387
|
+
: `Denied ${e.tool}: ${e.denyReason || 'unknown'}`,
|
|
4388
|
+
metadata: {
|
|
4389
|
+
tool: e.tool,
|
|
4390
|
+
creditsCharged: e.creditsCharged,
|
|
4391
|
+
allowed: e.allowed,
|
|
4392
|
+
denyReason: e.denyReason,
|
|
4393
|
+
durationMs: e.durationMs,
|
|
4394
|
+
},
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
// Sort by timestamp descending (newest first)
|
|
4398
|
+
timeline.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4399
|
+
// Apply limit
|
|
4400
|
+
const page = timeline.slice(0, limit);
|
|
4401
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4402
|
+
res.end(JSON.stringify({
|
|
4403
|
+
key: maskedKey,
|
|
4404
|
+
name: record.name,
|
|
4405
|
+
total: timeline.length,
|
|
4406
|
+
limit,
|
|
4407
|
+
events: page,
|
|
4408
|
+
}));
|
|
4409
|
+
}
|
|
4410
|
+
// ─── /keys/reserve — Credit reservations (hold, commit, release) ─────────
|
|
4411
|
+
handleListReservations(req, res) {
|
|
4412
|
+
if (!this.checkAdmin(req, res))
|
|
4413
|
+
return;
|
|
4414
|
+
const urlParts = req.url?.split('?') || [];
|
|
4415
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
4416
|
+
const keyParam = params.get('key');
|
|
4417
|
+
// Clean up expired reservations first
|
|
4418
|
+
this.cleanupExpiredReservations();
|
|
4419
|
+
let reservations = [...this.creditReservations.values()];
|
|
4420
|
+
if (keyParam) {
|
|
4421
|
+
const record = this.gate.store.resolveKeyRaw(keyParam);
|
|
4422
|
+
if (!record) {
|
|
4423
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4424
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
4425
|
+
return;
|
|
4426
|
+
}
|
|
4427
|
+
reservations = reservations.filter(r => r.key === record.key);
|
|
4428
|
+
}
|
|
4429
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4430
|
+
res.end(JSON.stringify({
|
|
4431
|
+
reservations: reservations.map(r => ({
|
|
4432
|
+
...r,
|
|
4433
|
+
key: (0, audit_1.maskKeyForAudit)(r.key),
|
|
4434
|
+
})),
|
|
4435
|
+
count: reservations.length,
|
|
4436
|
+
totalHeld: reservations.reduce((sum, r) => sum + r.credits, 0),
|
|
4437
|
+
}));
|
|
4438
|
+
}
|
|
4439
|
+
handleCreateReservation(req, res) {
|
|
4440
|
+
if (!this.checkAdmin(req, res))
|
|
4441
|
+
return;
|
|
4442
|
+
let body = '';
|
|
4443
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
4444
|
+
req.on('end', () => {
|
|
4445
|
+
let params;
|
|
4446
|
+
try {
|
|
4447
|
+
params = JSON.parse(body);
|
|
4448
|
+
}
|
|
4449
|
+
catch {
|
|
4450
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4451
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
4452
|
+
return;
|
|
4453
|
+
}
|
|
4454
|
+
if (!params.key || typeof params.key !== 'string') {
|
|
4455
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4456
|
+
res.end(JSON.stringify({ error: 'Missing required field: key' }));
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
if (!params.credits || typeof params.credits !== 'number' || params.credits <= 0) {
|
|
4460
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4461
|
+
res.end(JSON.stringify({ error: 'Missing or invalid credits (must be positive number)' }));
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4464
|
+
const record = this.gate.store.resolveKeyRaw(params.key);
|
|
4465
|
+
if (!record) {
|
|
4466
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4467
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
if (!record.active) {
|
|
4471
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4472
|
+
res.end(JSON.stringify({ error: 'Key is revoked' }));
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
if (record.suspended) {
|
|
4476
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4477
|
+
res.end(JSON.stringify({ error: 'Key is suspended' }));
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4480
|
+
// Cleanup expired before checking availability
|
|
4481
|
+
this.cleanupExpiredReservations();
|
|
4482
|
+
// Calculate available credits (total - held)
|
|
4483
|
+
const heldCredits = this.getHeldCredits(record.key);
|
|
4484
|
+
const available = record.credits - heldCredits;
|
|
4485
|
+
if (params.credits > available) {
|
|
4486
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4487
|
+
res.end(JSON.stringify({
|
|
4488
|
+
error: 'Insufficient available credits',
|
|
4489
|
+
available,
|
|
4490
|
+
held: heldCredits,
|
|
4491
|
+
total: record.credits,
|
|
4492
|
+
requested: params.credits,
|
|
4493
|
+
}));
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
// Max 50 active reservations per key
|
|
4497
|
+
const keyReservations = [...this.creditReservations.values()].filter(r => r.key === record.key);
|
|
4498
|
+
if (keyReservations.length >= 50) {
|
|
4499
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4500
|
+
res.end(JSON.stringify({ error: 'Maximum 50 active reservations per key' }));
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
const ttl = Math.min(3600, Math.max(10, params.ttlSeconds || 300)); // 10s – 1h, default 5m
|
|
4504
|
+
const reservation = {
|
|
4505
|
+
id: `rsv_${this.nextReservationId++}`,
|
|
4506
|
+
key: record.key,
|
|
4507
|
+
credits: params.credits,
|
|
4508
|
+
createdAt: new Date().toISOString(),
|
|
4509
|
+
expiresAt: new Date(Date.now() + ttl * 1000).toISOString(),
|
|
4510
|
+
memo: params.memo?.slice(0, 200),
|
|
4511
|
+
};
|
|
4512
|
+
this.creditReservations.set(reservation.id, reservation);
|
|
4513
|
+
this.audit.log('credits.reserved', 'admin', `Reserved ${params.credits} credits`, {
|
|
4514
|
+
reservationId: reservation.id,
|
|
4515
|
+
key: (0, audit_1.maskKeyForAudit)(record.key),
|
|
4516
|
+
credits: params.credits,
|
|
4517
|
+
ttlSeconds: ttl,
|
|
4518
|
+
});
|
|
4519
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
4520
|
+
res.end(JSON.stringify({
|
|
4521
|
+
...reservation,
|
|
4522
|
+
key: (0, audit_1.maskKeyForAudit)(reservation.key),
|
|
4523
|
+
available: available - params.credits,
|
|
4524
|
+
}));
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
handleCommitReservation(req, res) {
|
|
4528
|
+
if (!this.checkAdmin(req, res))
|
|
4529
|
+
return;
|
|
4530
|
+
let body = '';
|
|
4531
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
4532
|
+
req.on('end', () => {
|
|
4533
|
+
let params;
|
|
4534
|
+
try {
|
|
4535
|
+
params = JSON.parse(body);
|
|
4536
|
+
}
|
|
4537
|
+
catch {
|
|
4538
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4539
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
if (!params.reservationId || typeof params.reservationId !== 'string') {
|
|
4543
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4544
|
+
res.end(JSON.stringify({ error: 'Missing required field: reservationId' }));
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
const reservation = this.creditReservations.get(params.reservationId);
|
|
4548
|
+
if (!reservation) {
|
|
4549
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4550
|
+
res.end(JSON.stringify({ error: 'Reservation not found (may have expired)' }));
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
// Check if expired
|
|
4554
|
+
if (new Date(reservation.expiresAt).getTime() <= Date.now()) {
|
|
4555
|
+
this.creditReservations.delete(params.reservationId);
|
|
4556
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4557
|
+
res.end(JSON.stringify({ error: 'Reservation has expired' }));
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
const record = this.gate.store.resolveKeyRaw(reservation.key);
|
|
4561
|
+
if (!record) {
|
|
4562
|
+
this.creditReservations.delete(params.reservationId);
|
|
4563
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4564
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
// Deduct credits
|
|
4568
|
+
record.credits = Math.max(0, record.credits - reservation.credits);
|
|
4569
|
+
this.gate.store.save();
|
|
4570
|
+
// Remove reservation
|
|
4571
|
+
this.creditReservations.delete(params.reservationId);
|
|
4572
|
+
this.audit.log('credits.committed', 'admin', `Committed reservation: ${reservation.credits} credits deducted`, {
|
|
4573
|
+
reservationId: reservation.id,
|
|
4574
|
+
key: (0, audit_1.maskKeyForAudit)(record.key),
|
|
4575
|
+
credits: reservation.credits,
|
|
4576
|
+
remainingCredits: record.credits,
|
|
4577
|
+
});
|
|
4578
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4579
|
+
res.end(JSON.stringify({
|
|
4580
|
+
committed: {
|
|
4581
|
+
...reservation,
|
|
4582
|
+
key: (0, audit_1.maskKeyForAudit)(reservation.key),
|
|
4583
|
+
},
|
|
4584
|
+
remainingCredits: record.credits,
|
|
4585
|
+
}));
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4588
|
+
handleReleaseReservation(req, res) {
|
|
4589
|
+
if (!this.checkAdmin(req, res))
|
|
4590
|
+
return;
|
|
4591
|
+
let body = '';
|
|
4592
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
4593
|
+
req.on('end', () => {
|
|
4594
|
+
let params;
|
|
4595
|
+
try {
|
|
4596
|
+
params = JSON.parse(body);
|
|
4597
|
+
}
|
|
4598
|
+
catch {
|
|
4599
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4600
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
4601
|
+
return;
|
|
4602
|
+
}
|
|
4603
|
+
if (!params.reservationId || typeof params.reservationId !== 'string') {
|
|
4604
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4605
|
+
res.end(JSON.stringify({ error: 'Missing required field: reservationId' }));
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
const reservation = this.creditReservations.get(params.reservationId);
|
|
4609
|
+
if (!reservation) {
|
|
4610
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4611
|
+
res.end(JSON.stringify({ error: 'Reservation not found (may have expired)' }));
|
|
4612
|
+
return;
|
|
4613
|
+
}
|
|
4614
|
+
// Remove reservation (release the hold)
|
|
4615
|
+
this.creditReservations.delete(params.reservationId);
|
|
4616
|
+
this.audit.log('credits.released', 'admin', `Released reservation: ${reservation.credits} credits freed`, {
|
|
4617
|
+
reservationId: reservation.id,
|
|
4618
|
+
key: (0, audit_1.maskKeyForAudit)(reservation.key),
|
|
4619
|
+
credits: reservation.credits,
|
|
4620
|
+
});
|
|
4621
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4622
|
+
res.end(JSON.stringify({
|
|
4623
|
+
released: {
|
|
4624
|
+
...reservation,
|
|
4625
|
+
key: (0, audit_1.maskKeyForAudit)(reservation.key),
|
|
4626
|
+
},
|
|
4627
|
+
}));
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
/** Get total held credits for a key across all active reservations. */
|
|
4631
|
+
getHeldCredits(key) {
|
|
4632
|
+
let held = 0;
|
|
4633
|
+
for (const r of this.creditReservations.values()) {
|
|
4634
|
+
if (r.key === key)
|
|
4635
|
+
held += r.credits;
|
|
4636
|
+
}
|
|
4637
|
+
return held;
|
|
4638
|
+
}
|
|
4639
|
+
/** Remove expired reservations. */
|
|
4640
|
+
cleanupExpiredReservations() {
|
|
4641
|
+
const now = Date.now();
|
|
4642
|
+
for (const [id, r] of this.creditReservations) {
|
|
4643
|
+
if (new Date(r.expiresAt).getTime() <= now) {
|
|
4644
|
+
this.creditReservations.delete(id);
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4296
4648
|
// ─── /config/reload — Hot reload configuration from file ─────────────────
|
|
4297
4649
|
async handleConfigReload(req, res) {
|
|
4298
4650
|
if (req.method !== 'POST') {
|