paygate-mcp 3.2.0 → 3.4.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 +153 -0
- package/dist/admin-keys.d.ts +89 -0
- package/dist/admin-keys.d.ts.map +1 -0
- package/dist/admin-keys.js +178 -0
- package/dist/admin-keys.js.map +1 -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 +3 -0
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +35 -4
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +172 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +244 -0
- package/dist/plugin.js.map +1 -0
- package/dist/server.d.ts +25 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +260 -30
- package/dist/server.js.map +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
62
62
|
- **Scoped Tokens** — Issue short-lived `pgt_` tokens scoped to specific tools with auto-expiry (max 24h), HMAC-SHA256 signed, zero server-side state
|
|
63
63
|
- **Token Revocation List** — Revoke scoped tokens before expiry with O(1) lookup, auto-cleanup, Redis cross-instance sync, and admin API
|
|
64
64
|
- **Usage-Based Auto-Topup** — Automatically add credits when balance drops below a threshold with configurable daily limits, audit trail, webhook events, and Redis sync
|
|
65
|
+
- **Admin API Key Management** — Multiple admin keys with role-based permissions (super_admin, admin, viewer), file persistence, audit trail, and safety guards
|
|
66
|
+
- **Plugin System** — Extensible middleware hooks for custom billing logic, request/response transformation, custom endpoints, and lifecycle management
|
|
65
67
|
- **Refund on Failure** — Automatically refund credits when downstream tool calls fail
|
|
66
68
|
- **Webhook Events** — POST batched usage events to any URL for external billing/alerting
|
|
67
69
|
- **Config File Mode** — Load all settings from a JSON file (`--config`)
|
|
@@ -285,6 +287,9 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
|
|
|
285
287
|
| `/keys/ip` | POST | `X-Admin-Key` | Set IP allowlist (CIDR + exact match) |
|
|
286
288
|
| `/keys/search` | POST | `X-Admin-Key` | Search keys by tag values |
|
|
287
289
|
| `/keys/auto-topup` | POST | `X-Admin-Key` | Configure or disable auto-topup for a key |
|
|
290
|
+
| `/admin/keys` | GET | `X-Admin-Key` (super_admin) | List all admin keys (masked) |
|
|
291
|
+
| `/admin/keys` | POST | `X-Admin-Key` (super_admin) | Create a new admin key with role |
|
|
292
|
+
| `/admin/keys/revoke` | POST | `X-Admin-Key` (super_admin) | Revoke an admin key |
|
|
288
293
|
| `/limits` | POST | `X-Admin-Key` | Set spending limit on a key |
|
|
289
294
|
| `/usage` | GET | `X-Admin-Key` | Export usage data (JSON or CSV) |
|
|
290
295
|
| `/status` | GET | `X-Admin-Key` | Full dashboard with usage stats |
|
|
@@ -316,6 +321,7 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
|
|
|
316
321
|
| `/audit` | GET | `X-Admin-Key` | Query audit log (filter by type, actor, time) |
|
|
317
322
|
| `/audit/export` | GET | `X-Admin-Key` | Export full audit log (JSON or CSV) |
|
|
318
323
|
| `/audit/stats` | GET | `X-Admin-Key` | Audit log statistics |
|
|
324
|
+
| `/plugins` | GET | `X-Admin-Key` | List registered plugins with hook info |
|
|
319
325
|
| `/health` | GET | None | Health check (status, uptime, version, in-flight, Redis/webhook status) |
|
|
320
326
|
| `/` | GET | None | Root endpoint (endpoint list) |
|
|
321
327
|
|
|
@@ -1383,6 +1389,152 @@ gate.onAutoTopup = (apiKey, amount, newBalance) => {
|
|
|
1383
1389
|
const result = gate.evaluate(record.key, { name: 'expensive-tool' });
|
|
1384
1390
|
```
|
|
1385
1391
|
|
|
1392
|
+
### Admin API Key Management
|
|
1393
|
+
|
|
1394
|
+
Manage multiple admin keys with role-based permissions. The bootstrap admin key (from constructor or CLI) is always a `super_admin`.
|
|
1395
|
+
|
|
1396
|
+
**Roles:**
|
|
1397
|
+
| Role | Description |
|
|
1398
|
+
|------|-------------|
|
|
1399
|
+
| `super_admin` | Full access, including admin key management |
|
|
1400
|
+
| `admin` | All API key and system operations, but cannot manage admin keys |
|
|
1401
|
+
| `viewer` | Read-only access to status, usage, analytics, audit, etc. |
|
|
1402
|
+
|
|
1403
|
+
**Create an admin key (super_admin only):**
|
|
1404
|
+
|
|
1405
|
+
```bash
|
|
1406
|
+
curl -X POST http://localhost:3402/admin/keys \
|
|
1407
|
+
-H "X-Admin-Key: $ADMIN_KEY" \
|
|
1408
|
+
-H "Content-Type: application/json" \
|
|
1409
|
+
-d '{"name": "CI Bot", "role": "admin"}'
|
|
1410
|
+
# Returns: { "key": "ak_...", "name": "CI Bot", "role": "admin", "createdAt": "..." }
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
**List admin keys (super_admin only):**
|
|
1414
|
+
|
|
1415
|
+
```bash
|
|
1416
|
+
curl http://localhost:3402/admin/keys \
|
|
1417
|
+
-H "X-Admin-Key: $ADMIN_KEY"
|
|
1418
|
+
# Returns masked keys with roles, status, and last used timestamps
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
**Revoke an admin key (super_admin only):**
|
|
1422
|
+
|
|
1423
|
+
```bash
|
|
1424
|
+
curl -X POST http://localhost:3402/admin/keys/revoke \
|
|
1425
|
+
-H "X-Admin-Key: $ADMIN_KEY" \
|
|
1426
|
+
-H "Content-Type: application/json" \
|
|
1427
|
+
-d '{"key": "ak_..."}'
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
**Behavior:**
|
|
1431
|
+
- The default role for `POST /admin/keys` is `admin` if not specified.
|
|
1432
|
+
- Cannot revoke your own admin key (safety guard).
|
|
1433
|
+
- Cannot revoke the last `super_admin` key (safety guard).
|
|
1434
|
+
- `viewer` keys can access all read-only endpoints (GET) but are denied write operations (POST).
|
|
1435
|
+
- `admin` keys can create/revoke/rotate API keys, manage teams, tokens, etc. but cannot manage admin keys.
|
|
1436
|
+
- Admin keys are persisted to a separate file (`*-admin.json`) alongside the state file.
|
|
1437
|
+
- All operations are logged in the audit trail (`admin_key.created`, `admin_key.revoked`).
|
|
1438
|
+
- Webhook events are fired for admin key lifecycle changes.
|
|
1439
|
+
|
|
1440
|
+
### Plugin System
|
|
1441
|
+
|
|
1442
|
+
Add custom logic to PayGate with the plugin API. Plugins can intercept gate decisions, transform pricing, modify tool requests/responses, add custom HTTP endpoints, and hook into server lifecycle events.
|
|
1443
|
+
|
|
1444
|
+
```ts
|
|
1445
|
+
import { PayGateServer, PayGatePlugin } from 'paygate-mcp';
|
|
1446
|
+
|
|
1447
|
+
// Define a plugin
|
|
1448
|
+
const loggingPlugin: PayGatePlugin = {
|
|
1449
|
+
name: 'request-logger',
|
|
1450
|
+
version: '1.0.0',
|
|
1451
|
+
|
|
1452
|
+
// Gate hooks (sync — hot path)
|
|
1453
|
+
beforeGate: (ctx) => {
|
|
1454
|
+
// Return { allowed: false, reason: '...' } to short-circuit
|
|
1455
|
+
// Return null to continue normal evaluation
|
|
1456
|
+
if (ctx.toolName === 'dangerous_tool') {
|
|
1457
|
+
return { allowed: false, reason: 'tool_disabled' };
|
|
1458
|
+
}
|
|
1459
|
+
return null;
|
|
1460
|
+
},
|
|
1461
|
+
|
|
1462
|
+
afterGate: (ctx, decision) => {
|
|
1463
|
+
// Modify the gate decision after evaluation
|
|
1464
|
+
console.log(`${ctx.toolName}: ${decision.allowed ? 'allowed' : 'denied'}`);
|
|
1465
|
+
return decision;
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
transformPrice: (toolName, basePrice, args) => {
|
|
1469
|
+
// Return a number to override price, or null to keep base price
|
|
1470
|
+
if (toolName === 'premium_search') return basePrice * 2;
|
|
1471
|
+
return null;
|
|
1472
|
+
},
|
|
1473
|
+
|
|
1474
|
+
onDeny: (ctx, reason) => {
|
|
1475
|
+
// Called whenever a tool call is denied
|
|
1476
|
+
console.log(`Denied: ${ctx.toolName} — ${reason}`);
|
|
1477
|
+
},
|
|
1478
|
+
|
|
1479
|
+
// Tool hooks (async)
|
|
1480
|
+
beforeToolCall: async (ctx) => {
|
|
1481
|
+
// Modify the JSON-RPC request before forwarding
|
|
1482
|
+
return { ...ctx.request, params: { ...ctx.request.params, audit: true } };
|
|
1483
|
+
},
|
|
1484
|
+
|
|
1485
|
+
afterToolCall: async (ctx, response) => {
|
|
1486
|
+
// Modify the JSON-RPC response before returning to client
|
|
1487
|
+
return response;
|
|
1488
|
+
},
|
|
1489
|
+
|
|
1490
|
+
// HTTP hook (async)
|
|
1491
|
+
onRequest: (req, res) => {
|
|
1492
|
+
// Add custom endpoints — return true if handled
|
|
1493
|
+
if (req.url === '/custom/status') {
|
|
1494
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1495
|
+
res.end(JSON.stringify({ custom: true }));
|
|
1496
|
+
return true;
|
|
1497
|
+
}
|
|
1498
|
+
return false;
|
|
1499
|
+
},
|
|
1500
|
+
|
|
1501
|
+
// Lifecycle hooks (async)
|
|
1502
|
+
onStart: async () => { console.log('Plugin started'); },
|
|
1503
|
+
onStop: async () => { console.log('Plugin stopped'); },
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
// Register plugins with .use() (chainable)
|
|
1507
|
+
const server = new PayGateServer({ ... });
|
|
1508
|
+
server
|
|
1509
|
+
.use(loggingPlugin)
|
|
1510
|
+
.use(anotherPlugin);
|
|
1511
|
+
|
|
1512
|
+
await server.start();
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
**Hook types:**
|
|
1516
|
+
|
|
1517
|
+
| Hook | Sync/Async | Description |
|
|
1518
|
+
|------|-----------|-------------|
|
|
1519
|
+
| `beforeGate` | Sync | Short-circuit gate evaluation. First non-null result wins. |
|
|
1520
|
+
| `afterGate` | Sync | Modify gate decision. Cascading (each plugin sees previous result). |
|
|
1521
|
+
| `transformPrice` | Sync | Override tool pricing. First non-null number wins. |
|
|
1522
|
+
| `onDeny` | Sync | Notification on denial. All plugins called. |
|
|
1523
|
+
| `beforeToolCall` | Async | Modify JSON-RPC request before forwarding. Cascading. |
|
|
1524
|
+
| `afterToolCall` | Async | Modify JSON-RPC response before returning. Cascading. |
|
|
1525
|
+
| `onRequest` | Async | Add custom HTTP endpoints. First `true` return handles the request. |
|
|
1526
|
+
| `onStart` | Async | Called after server starts. Registration order. |
|
|
1527
|
+
| `onStop` | Async | Called before server stops. Reverse registration order. |
|
|
1528
|
+
|
|
1529
|
+
**Error isolation:** Plugin errors are caught and logged — a crashing plugin never takes down the server.
|
|
1530
|
+
|
|
1531
|
+
**List registered plugins (admin only):**
|
|
1532
|
+
|
|
1533
|
+
```bash
|
|
1534
|
+
curl http://localhost:3402/plugins -H "X-Admin-Key: $ADMIN_KEY"
|
|
1535
|
+
# { "count": 2, "plugins": [{ "name": "...", "version": "...", "hooks": ["beforeGate", ...] }] }
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1386
1538
|
### Horizontal Scaling (Redis)
|
|
1387
1539
|
|
|
1388
1540
|
Enable Redis-backed state for multi-process deployments. Multiple PayGate instances share API keys, credits, and usage data through Redis:
|
|
@@ -1629,6 +1781,7 @@ const result = await client.callTool('search', { query: 'hello' });
|
|
|
1629
1781
|
- [x] Scoped tokens — Short-lived `pgt_` tokens with tool ACL narrowing, HMAC-SHA256 signed, zero server-side state
|
|
1630
1782
|
- [x] Token revocation list — Revoke scoped tokens before expiry with O(1) lookup, auto-cleanup, Redis sync
|
|
1631
1783
|
- [x] Usage-based auto-topup — Automatically refill credits when balance drops below threshold with daily limits
|
|
1784
|
+
- [x] Admin API key management — Multiple admin keys with role-based permissions (super_admin, admin, viewer)
|
|
1632
1785
|
|
|
1633
1786
|
## Requirements
|
|
1634
1787
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdminKeyManager — Multiple admin keys with role-based permissions.
|
|
3
|
+
*
|
|
4
|
+
* Roles (hierarchical):
|
|
5
|
+
* - super_admin: Full access, including admin key management
|
|
6
|
+
* - admin: All API key and system operations, but cannot manage admin keys
|
|
7
|
+
* - viewer: Read-only access to status, usage, analytics, audit, etc.
|
|
8
|
+
*
|
|
9
|
+
* The bootstrap admin key (from constructor) is always a super_admin.
|
|
10
|
+
* Supports file-based persistence (separate file from API key state).
|
|
11
|
+
*/
|
|
12
|
+
export type AdminRole = 'super_admin' | 'admin' | 'viewer';
|
|
13
|
+
export interface AdminKeyRecord {
|
|
14
|
+
/** The admin API key (ak_ prefix, or legacy admin_ prefix for bootstrap) */
|
|
15
|
+
key: string;
|
|
16
|
+
/** Human-readable name */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Permission level */
|
|
19
|
+
role: AdminRole;
|
|
20
|
+
/** ISO timestamp when the key was created */
|
|
21
|
+
createdAt: string;
|
|
22
|
+
/** Who created this key (masked key or 'bootstrap') */
|
|
23
|
+
createdBy: string;
|
|
24
|
+
/** Whether this key is active (can be revoked) */
|
|
25
|
+
active: boolean;
|
|
26
|
+
/** Last time this key was used for authentication (ISO timestamp) */
|
|
27
|
+
lastUsedAt: string | null;
|
|
28
|
+
}
|
|
29
|
+
/** Role hierarchy for permission checks (higher = more permissions) */
|
|
30
|
+
export declare const ROLE_HIERARCHY: Record<AdminRole, number>;
|
|
31
|
+
/** Valid role names */
|
|
32
|
+
export declare const VALID_ROLES: AdminRole[];
|
|
33
|
+
export declare class AdminKeyManager {
|
|
34
|
+
private keys;
|
|
35
|
+
private readonly filePath;
|
|
36
|
+
constructor(filePath?: string);
|
|
37
|
+
/** Load admin keys from file. */
|
|
38
|
+
private load;
|
|
39
|
+
/** Save admin keys to file. */
|
|
40
|
+
save(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Bootstrap the manager with the initial admin key.
|
|
43
|
+
* This is the key passed to PayGateServer constructor (or auto-generated).
|
|
44
|
+
* It always gets super_admin role. Skips if the key is already known.
|
|
45
|
+
*/
|
|
46
|
+
bootstrap(key: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Validate an admin key. Returns the record if valid, null otherwise.
|
|
49
|
+
* Updates lastUsedAt on successful validation.
|
|
50
|
+
*/
|
|
51
|
+
validate(key: string): AdminKeyRecord | null;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a key has at least the minimum required role.
|
|
54
|
+
*/
|
|
55
|
+
hasRole(key: string, minRole: AdminRole): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Create a new admin key with ak_ prefix.
|
|
58
|
+
*/
|
|
59
|
+
create(name: string, role: AdminRole, createdBy: string): AdminKeyRecord;
|
|
60
|
+
/**
|
|
61
|
+
* Revoke an admin key.
|
|
62
|
+
* Cannot revoke the last active super_admin key (safety check).
|
|
63
|
+
*/
|
|
64
|
+
revoke(key: string): {
|
|
65
|
+
success: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* List all admin keys.
|
|
70
|
+
*/
|
|
71
|
+
list(): AdminKeyRecord[];
|
|
72
|
+
/**
|
|
73
|
+
* Get a specific admin key record.
|
|
74
|
+
*/
|
|
75
|
+
get(key: string): AdminKeyRecord | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Number of active admin keys.
|
|
78
|
+
*/
|
|
79
|
+
get activeCount(): number;
|
|
80
|
+
/**
|
|
81
|
+
* Export state for persistence.
|
|
82
|
+
*/
|
|
83
|
+
toJSON(): AdminKeyRecord[];
|
|
84
|
+
/**
|
|
85
|
+
* Import state from array. Clears existing keys.
|
|
86
|
+
*/
|
|
87
|
+
fromJSON(records: AdminKeyRecord[]): void;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=admin-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-keys.d.ts","sourceRoot":"","sources":["../src/admin-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,uEAAuE;AACvE,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAIpD,CAAC;AAEF,uBAAuB;AACvB,eAAO,MAAM,WAAW,EAAE,SAAS,EAAuC,CAAC;AAI3E,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAA0C;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;gBAE7B,QAAQ,CAAC,EAAE,MAAM;IAO7B,iCAAiC;IACjC,OAAO,CAAC,IAAI;IAcZ,+BAA+B;IAC/B,IAAI,IAAI,IAAI;IAOZ;;;;OAIG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAgB5B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAQ5C;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,OAAO;IAQjD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc;IAgBxE;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAqBzD;;OAEG;IACH,IAAI,IAAI,cAAc,EAAE;IAIxB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI5C;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAID;;OAEG;IACH,MAAM,IAAI,cAAc,EAAE;IAI1B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI;CAM1C"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AdminKeyManager — Multiple admin keys with role-based permissions.
|
|
4
|
+
*
|
|
5
|
+
* Roles (hierarchical):
|
|
6
|
+
* - super_admin: Full access, including admin key management
|
|
7
|
+
* - admin: All API key and system operations, but cannot manage admin keys
|
|
8
|
+
* - viewer: Read-only access to status, usage, analytics, audit, etc.
|
|
9
|
+
*
|
|
10
|
+
* The bootstrap admin key (from constructor) is always a super_admin.
|
|
11
|
+
* Supports file-based persistence (separate file from API key state).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.AdminKeyManager = exports.VALID_ROLES = exports.ROLE_HIERARCHY = void 0;
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const fs_1 = require("fs");
|
|
17
|
+
/** Role hierarchy for permission checks (higher = more permissions) */
|
|
18
|
+
exports.ROLE_HIERARCHY = {
|
|
19
|
+
super_admin: 3,
|
|
20
|
+
admin: 2,
|
|
21
|
+
viewer: 1,
|
|
22
|
+
};
|
|
23
|
+
/** Valid role names */
|
|
24
|
+
exports.VALID_ROLES = ['super_admin', 'admin', 'viewer'];
|
|
25
|
+
// ─── AdminKeyManager ─────────────────────────────────────────────────────────
|
|
26
|
+
class AdminKeyManager {
|
|
27
|
+
keys = new Map();
|
|
28
|
+
filePath;
|
|
29
|
+
constructor(filePath) {
|
|
30
|
+
this.filePath = filePath || null;
|
|
31
|
+
if (this.filePath)
|
|
32
|
+
this.load();
|
|
33
|
+
}
|
|
34
|
+
// ─── Persistence ─────────────────────────────────────────────────────────
|
|
35
|
+
/** Load admin keys from file. */
|
|
36
|
+
load() {
|
|
37
|
+
if (!this.filePath || !(0, fs_1.existsSync)(this.filePath))
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse((0, fs_1.readFileSync)(this.filePath, 'utf-8'));
|
|
41
|
+
if (Array.isArray(data)) {
|
|
42
|
+
for (const record of data) {
|
|
43
|
+
if (record && record.key) {
|
|
44
|
+
this.keys.set(record.key, record);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* ignore corrupted file */ }
|
|
50
|
+
}
|
|
51
|
+
/** Save admin keys to file. */
|
|
52
|
+
save() {
|
|
53
|
+
if (!this.filePath)
|
|
54
|
+
return;
|
|
55
|
+
(0, fs_1.writeFileSync)(this.filePath, JSON.stringify(this.toJSON(), null, 2));
|
|
56
|
+
}
|
|
57
|
+
// ─── Bootstrap ───────────────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Bootstrap the manager with the initial admin key.
|
|
60
|
+
* This is the key passed to PayGateServer constructor (or auto-generated).
|
|
61
|
+
* It always gets super_admin role. Skips if the key is already known.
|
|
62
|
+
*/
|
|
63
|
+
bootstrap(key) {
|
|
64
|
+
if (this.keys.has(key))
|
|
65
|
+
return;
|
|
66
|
+
this.keys.set(key, {
|
|
67
|
+
key,
|
|
68
|
+
name: 'Bootstrap Admin',
|
|
69
|
+
role: 'super_admin',
|
|
70
|
+
createdAt: new Date().toISOString(),
|
|
71
|
+
createdBy: 'bootstrap',
|
|
72
|
+
active: true,
|
|
73
|
+
lastUsedAt: null,
|
|
74
|
+
});
|
|
75
|
+
this.save();
|
|
76
|
+
}
|
|
77
|
+
// ─── Validation ──────────────────────────────────────────────────────────
|
|
78
|
+
/**
|
|
79
|
+
* Validate an admin key. Returns the record if valid, null otherwise.
|
|
80
|
+
* Updates lastUsedAt on successful validation.
|
|
81
|
+
*/
|
|
82
|
+
validate(key) {
|
|
83
|
+
if (!key)
|
|
84
|
+
return null;
|
|
85
|
+
const record = this.keys.get(key);
|
|
86
|
+
if (!record || !record.active)
|
|
87
|
+
return null;
|
|
88
|
+
record.lastUsedAt = new Date().toISOString();
|
|
89
|
+
return record;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a key has at least the minimum required role.
|
|
93
|
+
*/
|
|
94
|
+
hasRole(key, minRole) {
|
|
95
|
+
const record = this.validate(key);
|
|
96
|
+
if (!record)
|
|
97
|
+
return false;
|
|
98
|
+
return exports.ROLE_HIERARCHY[record.role] >= exports.ROLE_HIERARCHY[minRole];
|
|
99
|
+
}
|
|
100
|
+
// ─── CRUD ────────────────────────────────────────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Create a new admin key with ak_ prefix.
|
|
103
|
+
*/
|
|
104
|
+
create(name, role, createdBy) {
|
|
105
|
+
const key = `ak_${(0, crypto_1.randomBytes)(16).toString('hex')}`;
|
|
106
|
+
const record = {
|
|
107
|
+
key,
|
|
108
|
+
name,
|
|
109
|
+
role,
|
|
110
|
+
createdAt: new Date().toISOString(),
|
|
111
|
+
createdBy,
|
|
112
|
+
active: true,
|
|
113
|
+
lastUsedAt: null,
|
|
114
|
+
};
|
|
115
|
+
this.keys.set(key, record);
|
|
116
|
+
this.save();
|
|
117
|
+
return record;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Revoke an admin key.
|
|
121
|
+
* Cannot revoke the last active super_admin key (safety check).
|
|
122
|
+
*/
|
|
123
|
+
revoke(key) {
|
|
124
|
+
const record = this.keys.get(key);
|
|
125
|
+
if (!record)
|
|
126
|
+
return { success: false, error: 'Admin key not found' };
|
|
127
|
+
if (!record.active)
|
|
128
|
+
return { success: false, error: 'Admin key already revoked' };
|
|
129
|
+
// Prevent revoking the last super_admin
|
|
130
|
+
if (record.role === 'super_admin') {
|
|
131
|
+
const activeSuperAdmins = Array.from(this.keys.values())
|
|
132
|
+
.filter(k => k.active && k.role === 'super_admin');
|
|
133
|
+
if (activeSuperAdmins.length <= 1) {
|
|
134
|
+
return { success: false, error: 'Cannot revoke the last super_admin key' };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
record.active = false;
|
|
138
|
+
this.save();
|
|
139
|
+
return { success: true };
|
|
140
|
+
}
|
|
141
|
+
// ─── Query ───────────────────────────────────────────────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* List all admin keys.
|
|
144
|
+
*/
|
|
145
|
+
list() {
|
|
146
|
+
return Array.from(this.keys.values());
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get a specific admin key record.
|
|
150
|
+
*/
|
|
151
|
+
get(key) {
|
|
152
|
+
return this.keys.get(key);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Number of active admin keys.
|
|
156
|
+
*/
|
|
157
|
+
get activeCount() {
|
|
158
|
+
return Array.from(this.keys.values()).filter(k => k.active).length;
|
|
159
|
+
}
|
|
160
|
+
// ─── Serialization ──────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Export state for persistence.
|
|
163
|
+
*/
|
|
164
|
+
toJSON() {
|
|
165
|
+
return Array.from(this.keys.values());
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Import state from array. Clears existing keys.
|
|
169
|
+
*/
|
|
170
|
+
fromJSON(records) {
|
|
171
|
+
this.keys.clear();
|
|
172
|
+
for (const record of records) {
|
|
173
|
+
this.keys.set(record.key, record);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.AdminKeyManager = AdminKeyManager;
|
|
178
|
+
//# sourceMappingURL=admin-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-keys.js","sourceRoot":"","sources":["../src/admin-keys.ts"],"names":[],"mappings":"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"}
|
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.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' | 'token.created' | 'token.revoked' | 'billing.refund' | 'key.auto_topup_configured' | 'key.auto_topped_up';
|
|
8
|
+
export type AuditEventType = 'key.created' | 'key.revoked' | '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' | 'token.created' | 'token.revoked' | 'billing.refund' | 'key.auto_topup_configured' | 'key.auto_topped_up' | 'admin_key.created' | 'admin_key.revoked';
|
|
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,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,GAE7B,eAAe,GACf,eAAe,GAEf,gBAAgB,GAEhB,2BAA2B,GAC3B,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/gate.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { RateLimiter } from './rate-limiter';
|
|
|
16
16
|
import { UsageMeter } from './meter';
|
|
17
17
|
import { WebhookEmitter } from './webhook';
|
|
18
18
|
import { QuotaTracker } from './quota';
|
|
19
|
+
import { PluginManager } from './plugin';
|
|
19
20
|
export declare class Gate {
|
|
20
21
|
readonly store: KeyStore;
|
|
21
22
|
readonly rateLimiter: RateLimiter;
|
|
@@ -23,6 +24,8 @@ export declare class Gate {
|
|
|
23
24
|
readonly webhook: WebhookEmitter | null;
|
|
24
25
|
readonly quotaTracker: QuotaTracker;
|
|
25
26
|
private readonly config;
|
|
27
|
+
/** Optional plugin manager for extensible hooks. */
|
|
28
|
+
pluginManager?: PluginManager;
|
|
26
29
|
/** Optional team-level budget/quota checker injected by server. */
|
|
27
30
|
teamChecker?: (apiKey: string, credits: number) => {
|
|
28
31
|
allowed: boolean;
|
package/dist/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAe,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC7I,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
|
package/dist/gate.js
CHANGED
|
@@ -25,6 +25,8 @@ class Gate {
|
|
|
25
25
|
webhook;
|
|
26
26
|
quotaTracker;
|
|
27
27
|
config;
|
|
28
|
+
/** Optional plugin manager for extensible hooks. */
|
|
29
|
+
pluginManager;
|
|
28
30
|
/** Optional team-level budget/quota checker injected by server. */
|
|
29
31
|
teamChecker;
|
|
30
32
|
/** Optional team usage recorder injected by server. */
|
|
@@ -52,6 +54,25 @@ class Gate {
|
|
|
52
54
|
evaluate(apiKey, toolCall, clientIp, scopedTokenTools) {
|
|
53
55
|
const toolName = toolCall.name;
|
|
54
56
|
const creditsRequired = this.getToolPrice(toolName, toolCall.arguments);
|
|
57
|
+
// Plugin: beforeGate — short-circuit if any plugin returns a decision
|
|
58
|
+
if (this.pluginManager && this.pluginManager.count > 0) {
|
|
59
|
+
const keyRecord = apiKey ? this.store.getKey(apiKey) : undefined;
|
|
60
|
+
const pluginCtx = { apiKey, toolName, toolArgs: toolCall.arguments, clientIp, keyRecord: keyRecord || undefined };
|
|
61
|
+
const override = this.pluginManager.executeBeforeGate(pluginCtx);
|
|
62
|
+
if (override) {
|
|
63
|
+
const decision = {
|
|
64
|
+
allowed: override.allowed,
|
|
65
|
+
reason: override.reason || (override.allowed ? undefined : 'plugin_denied'),
|
|
66
|
+
creditsCharged: override.creditsCharged ?? 0,
|
|
67
|
+
remainingCredits: keyRecord?.credits ?? 0,
|
|
68
|
+
};
|
|
69
|
+
if (!decision.allowed) {
|
|
70
|
+
this.pluginManager.executeOnDeny(pluginCtx, decision.reason || 'plugin_denied');
|
|
71
|
+
this.recordEvent(apiKey || 'none', keyRecord?.name || '', toolName, 0, false, decision.reason);
|
|
72
|
+
}
|
|
73
|
+
return this.pluginManager.executeAfterGate(pluginCtx, decision);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
55
76
|
// Step 1: API key present?
|
|
56
77
|
if (!apiKey) {
|
|
57
78
|
this.recordEvent(apiKey || 'none', '', toolName, 0, false, 'missing_api_key');
|
|
@@ -192,7 +213,13 @@ class Gate {
|
|
|
192
213
|
this.checkAutoTopup(apiKey);
|
|
193
214
|
const remaining = this.store.getKey(apiKey)?.credits ?? 0;
|
|
194
215
|
this.recordEvent(apiKey, keyRecord.name, toolName, creditsRequired, true, undefined, keyRecord.namespace);
|
|
195
|
-
|
|
216
|
+
let decision = { allowed: true, creditsCharged: creditsRequired, remainingCredits: remaining };
|
|
217
|
+
// Plugin: afterGate — let plugins modify the final decision
|
|
218
|
+
if (this.pluginManager && this.pluginManager.count > 0) {
|
|
219
|
+
const pluginCtx = { apiKey, toolName, toolArgs: toolCall.arguments, clientIp, keyRecord };
|
|
220
|
+
decision = this.pluginManager.executeAfterGate(pluginCtx, decision);
|
|
221
|
+
}
|
|
222
|
+
return decision;
|
|
196
223
|
}
|
|
197
224
|
/**
|
|
198
225
|
* Evaluate a batch of tool calls atomically (all-or-nothing).
|
|
@@ -535,15 +562,19 @@ class Gate {
|
|
|
535
562
|
*/
|
|
536
563
|
getToolPrice(toolName, args) {
|
|
537
564
|
const override = this.config.toolPricing[toolName];
|
|
538
|
-
|
|
565
|
+
let price = override ? override.creditsPerCall : this.config.defaultCreditsPerCall;
|
|
539
566
|
// Dynamic pricing: add per-KB surcharge for input size
|
|
540
567
|
if (override?.creditsPerKbInput && override.creditsPerKbInput > 0 && args) {
|
|
541
568
|
const inputBytes = Buffer.byteLength(JSON.stringify(args), 'utf-8');
|
|
542
569
|
const inputKb = inputBytes / 1024;
|
|
543
570
|
const surcharge = Math.ceil(inputKb * override.creditsPerKbInput);
|
|
544
|
-
|
|
571
|
+
price = price + surcharge;
|
|
572
|
+
}
|
|
573
|
+
// Plugin: transformPrice — let plugins override the final price
|
|
574
|
+
if (this.pluginManager && this.pluginManager.count > 0) {
|
|
575
|
+
price = this.pluginManager.executeTransformPrice(toolName, price, args);
|
|
545
576
|
}
|
|
546
|
-
return
|
|
577
|
+
return price;
|
|
547
578
|
}
|
|
548
579
|
/**
|
|
549
580
|
* Get full status for dashboard.
|