paygate-mcp 5.0.0 → 5.1.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 +40 -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/expiry-scanner.d.ts +99 -0
- package/dist/expiry-scanner.d.ts.map +1 -0
- package/dist/expiry-scanner.js +216 -0
- package/dist/expiry-scanner.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +5 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +7 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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
|
@@ -77,6 +77,7 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
77
77
|
- **Webhook Delivery Log** — `GET /webhooks/log` returns a queryable log of all webhook delivery attempts with timestamps, HTTP status codes, response times, success/failure, retry attempts, event counts, and event types — filter by success status, time range, and limit
|
|
78
78
|
- **Webhook Pause/Resume** — `POST /webhooks/pause` and `POST /webhooks/resume` temporarily halt webhook delivery during maintenance — events are buffered (not lost) and flushed on resume, with pause state visible in `/webhooks/stats`
|
|
79
79
|
- **Key Aliases** — `POST /keys/alias` assigns human-readable aliases (e.g. `my-service`, `prod-backend`) to API keys — use aliases in any admin endpoint (topup, revoke, suspend, resume, clone, transfer, usage) instead of opaque key IDs, with uniqueness enforcement, format validation, state file persistence, and audit trail
|
|
80
|
+
- **Key Expiry Scanner** — Proactive background scanner that detects expiring API keys before they expire — configurable scan interval and notification thresholds (default: 7d, 24h, 1h), de-duplicated `key.expiry_warning` webhook events, audit trail, `GET /keys/expiring?within=86400` query endpoint, and graceful shutdown
|
|
80
81
|
- **Config Hot Reload** — `POST /config/reload` reloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart
|
|
81
82
|
- **Webhook Events** — POST batched usage events to any URL for external billing/alerting
|
|
82
83
|
- **Config File Mode** — Load all settings from a JSON file (`--config`)
|
|
@@ -359,6 +360,7 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
|
|
|
359
360
|
| `/webhooks/pause` | POST | `X-Admin-Key` | Pause webhook delivery (events buffered until resumed) |
|
|
360
361
|
| `/webhooks/resume` | POST | `X-Admin-Key` | Resume webhook delivery and flush buffered events |
|
|
361
362
|
| `/keys/alias` | POST | `X-Admin-Key` | Set or clear a human-readable alias for an API key |
|
|
363
|
+
| `/keys/expiring` | GET | `X-Admin-Key` | List keys expiring within a time window (`?within=86400` seconds) |
|
|
362
364
|
| `/config/reload` | POST | `X-Admin-Key` | Hot-reload config file (pricing, rate limits, webhooks, quotas) |
|
|
363
365
|
| `/health` | GET | None | Health check (status, uptime, version, in-flight, Redis/webhook status) |
|
|
364
366
|
| `/` | GET | None | Root endpoint (endpoint list) |
|
|
@@ -1237,6 +1239,44 @@ curl -X POST http://localhost:3402/keys/alias \
|
|
|
1237
1239
|
| Clone | Cloned keys do **not** inherit the source key's alias |
|
|
1238
1240
|
| Audit | `key.alias_set` event logged for every set/clear operation |
|
|
1239
1241
|
|
|
1242
|
+
### Key Expiry Scanner
|
|
1243
|
+
|
|
1244
|
+
Proactive background scanner that detects API keys approaching expiration and sends webhook notifications before they expire — even if the keys are not actively being used:
|
|
1245
|
+
|
|
1246
|
+
```bash
|
|
1247
|
+
# Query keys expiring within 24 hours (default)
|
|
1248
|
+
curl http://localhost:3402/keys/expiring \
|
|
1249
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
1250
|
+
# → { "within": 86400, "count": 2, "scanner": { ... }, "keys": [ ... ] }
|
|
1251
|
+
|
|
1252
|
+
# Query keys expiring within 7 days
|
|
1253
|
+
curl http://localhost:3402/keys/expiring?within=604800 \
|
|
1254
|
+
-H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
Configure the scanner in your config file:
|
|
1258
|
+
|
|
1259
|
+
```json
|
|
1260
|
+
{
|
|
1261
|
+
"expiryScanner": {
|
|
1262
|
+
"enabled": true,
|
|
1263
|
+
"intervalSeconds": 3600,
|
|
1264
|
+
"thresholds": [604800, 86400, 3600]
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
| Field | Description |
|
|
1270
|
+
|-------|-------------|
|
|
1271
|
+
| `enabled` | Enable/disable the background scanner. Default: `true` |
|
|
1272
|
+
| `intervalSeconds` | How often to scan (seconds). Default: `3600` (1 hour). Min: 60 |
|
|
1273
|
+
| `thresholds` | Seconds before expiry to notify. Default: `[604800, 86400, 3600]` (7d, 24h, 1h) |
|
|
1274
|
+
| Webhook | Fires `key.expiry_warning` events with key name, alias, namespace, expiry time, and remaining seconds |
|
|
1275
|
+
| De-duplication | Each key+threshold pair is only notified once (no duplicate alerts) |
|
|
1276
|
+
| Progressive | Largest threshold fires first, then progressively smaller thresholds on subsequent scans |
|
|
1277
|
+
| Audit | `key.expiry_warning` event logged for every notification |
|
|
1278
|
+
| Endpoint | `GET /keys/expiring?within=N` lists keys expiring within N seconds (default: 86400) |
|
|
1279
|
+
|
|
1240
1280
|
### IP Allowlisting
|
|
1241
1281
|
|
|
1242
1282
|
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' | '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';
|
|
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' | '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';
|
|
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":"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"}
|
|
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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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExpiryScanner — Proactive background scanner for expiring API keys.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the reactive key_expiry_soon alert (which only fires during gate evaluation),
|
|
5
|
+
* this scanner runs on a configurable interval and catches expiring keys even when idle.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Configurable scan interval (default: 1 hour)
|
|
9
|
+
* - Multiple notification thresholds (e.g., 7d, 24h, 1h before expiry)
|
|
10
|
+
* - De-duplication: same key+threshold pair is only notified once
|
|
11
|
+
* - Fires key.expiry_warning webhook events
|
|
12
|
+
* - Audit trail for all notifications
|
|
13
|
+
* - Graceful shutdown (clears interval timer)
|
|
14
|
+
*/
|
|
15
|
+
import { ApiKeyRecord } from './types';
|
|
16
|
+
export interface ExpiryScannerConfig {
|
|
17
|
+
/** Whether the scanner is enabled. Default: true when thresholds are configured. */
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
/** How often to scan, in seconds. Default: 3600 (1 hour). Min: 60. */
|
|
20
|
+
intervalSeconds: number;
|
|
21
|
+
/** Seconds before expiry to send notifications. Default: [604800, 86400, 3600] (7d, 24h, 1h). */
|
|
22
|
+
thresholds: number[];
|
|
23
|
+
}
|
|
24
|
+
export interface ExpiryWarning {
|
|
25
|
+
/** The API key (full, for internal use — mask before exposing) */
|
|
26
|
+
key: string;
|
|
27
|
+
/** Key name */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Key alias (if set) */
|
|
30
|
+
alias?: string;
|
|
31
|
+
/** Key namespace */
|
|
32
|
+
namespace: string;
|
|
33
|
+
/** ISO string when the key expires */
|
|
34
|
+
expiresAt: string;
|
|
35
|
+
/** Seconds remaining until expiry */
|
|
36
|
+
remainingSeconds: number;
|
|
37
|
+
/** Human-readable time remaining */
|
|
38
|
+
remainingHuman: string;
|
|
39
|
+
/** Which threshold triggered this warning (seconds) */
|
|
40
|
+
thresholdSeconds: number;
|
|
41
|
+
}
|
|
42
|
+
export declare const DEFAULT_EXPIRY_SCANNER_CONFIG: ExpiryScannerConfig;
|
|
43
|
+
export declare class ExpiryScanner {
|
|
44
|
+
private config;
|
|
45
|
+
private timer;
|
|
46
|
+
/** De-duplication: "keyPrefix:threshold" → timestamp of last notification */
|
|
47
|
+
private readonly notified;
|
|
48
|
+
/** Callback for each warning — wired by the server to emit webhooks/audit */
|
|
49
|
+
onWarning: ((warning: ExpiryWarning) => void) | null;
|
|
50
|
+
/** Key provider — returns all key records for scanning */
|
|
51
|
+
private getKeys;
|
|
52
|
+
constructor(config?: Partial<ExpiryScannerConfig>);
|
|
53
|
+
/**
|
|
54
|
+
* Start the background scanner.
|
|
55
|
+
* @param getKeys Function that returns all key records to scan
|
|
56
|
+
*/
|
|
57
|
+
start(getKeys: () => ApiKeyRecord[]): void;
|
|
58
|
+
/**
|
|
59
|
+
* Stop the scanner and clear all state.
|
|
60
|
+
*/
|
|
61
|
+
destroy(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Run a scan now (also called by the interval timer).
|
|
64
|
+
* Returns warnings found in this scan.
|
|
65
|
+
*/
|
|
66
|
+
scan(): ExpiryWarning[];
|
|
67
|
+
/**
|
|
68
|
+
* Query keys expiring within a time window (for the admin endpoint).
|
|
69
|
+
* Does NOT trigger notifications — this is a read-only query.
|
|
70
|
+
*/
|
|
71
|
+
static queryExpiring(keys: ApiKeyRecord[], withinSeconds: number): Array<{
|
|
72
|
+
keyPrefix: string;
|
|
73
|
+
name: string;
|
|
74
|
+
alias?: string;
|
|
75
|
+
namespace: string;
|
|
76
|
+
expiresAt: string;
|
|
77
|
+
remainingSeconds: number;
|
|
78
|
+
remainingHuman: string;
|
|
79
|
+
suspended: boolean;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Get scanner status (for /health or diagnostics).
|
|
83
|
+
*/
|
|
84
|
+
get status(): {
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
intervalSeconds: number;
|
|
87
|
+
thresholds: number[];
|
|
88
|
+
notifiedCount: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Clear de-duplication state (for testing).
|
|
92
|
+
*/
|
|
93
|
+
clearNotified(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Update config at runtime (for config hot-reload).
|
|
96
|
+
*/
|
|
97
|
+
updateConfig(config: Partial<ExpiryScannerConfig>): void;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=expiry-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry-scanner.d.ts","sourceRoot":"","sources":["../src/expiry-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,OAAO,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe;IACf,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,6BAA6B,EAAE,mBAI3C,CAAC;AAIF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAA+C;IAC5D,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,6EAA6E;IAC7E,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC5D,0DAA0D;IAC1D,OAAO,CAAC,OAAO,CAAuC;gBAE1C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IAQjD;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,YAAY,EAAE,GAAG,IAAI;IAU1C;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;;OAGG;IACH,IAAI,IAAI,aAAa,EAAE;IAiEvB;;;OAGG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,KAAK,CAAC;QACvE,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IA0CF;;OAEG;IACH,IAAI,MAAM,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAOvG;IAED;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;CAWzD"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ExpiryScanner — Proactive background scanner for expiring API keys.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the reactive key_expiry_soon alert (which only fires during gate evaluation),
|
|
6
|
+
* this scanner runs on a configurable interval and catches expiring keys even when idle.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Configurable scan interval (default: 1 hour)
|
|
10
|
+
* - Multiple notification thresholds (e.g., 7d, 24h, 1h before expiry)
|
|
11
|
+
* - De-duplication: same key+threshold pair is only notified once
|
|
12
|
+
* - Fires key.expiry_warning webhook events
|
|
13
|
+
* - Audit trail for all notifications
|
|
14
|
+
* - Graceful shutdown (clears interval timer)
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ExpiryScanner = exports.DEFAULT_EXPIRY_SCANNER_CONFIG = void 0;
|
|
18
|
+
exports.DEFAULT_EXPIRY_SCANNER_CONFIG = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
intervalSeconds: 3600,
|
|
21
|
+
thresholds: [604800, 86400, 3600], // 7 days, 24 hours, 1 hour
|
|
22
|
+
};
|
|
23
|
+
// ─── Scanner Class ────────────────────────────────────────────────────────────
|
|
24
|
+
class ExpiryScanner {
|
|
25
|
+
config;
|
|
26
|
+
timer = null;
|
|
27
|
+
/** De-duplication: "keyPrefix:threshold" → timestamp of last notification */
|
|
28
|
+
notified = new Map();
|
|
29
|
+
/** Callback for each warning — wired by the server to emit webhooks/audit */
|
|
30
|
+
onWarning = null;
|
|
31
|
+
/** Key provider — returns all key records for scanning */
|
|
32
|
+
getKeys = null;
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this.config = { ...exports.DEFAULT_EXPIRY_SCANNER_CONFIG, ...config };
|
|
35
|
+
// Enforce minimum interval
|
|
36
|
+
if (this.config.intervalSeconds < 60)
|
|
37
|
+
this.config.intervalSeconds = 60;
|
|
38
|
+
// Sort thresholds descending (largest first) for consistent scanning
|
|
39
|
+
this.config.thresholds = [...this.config.thresholds].sort((a, b) => b - a);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start the background scanner.
|
|
43
|
+
* @param getKeys Function that returns all key records to scan
|
|
44
|
+
*/
|
|
45
|
+
start(getKeys) {
|
|
46
|
+
if (!this.config.enabled)
|
|
47
|
+
return;
|
|
48
|
+
if (this.config.thresholds.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
this.getKeys = getKeys;
|
|
51
|
+
// Run immediately on start, then on interval
|
|
52
|
+
this.scan();
|
|
53
|
+
this.timer = setInterval(() => this.scan(), this.config.intervalSeconds * 1000);
|
|
54
|
+
this.timer.unref(); // Don't prevent process exit
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stop the scanner and clear all state.
|
|
58
|
+
*/
|
|
59
|
+
destroy() {
|
|
60
|
+
if (this.timer) {
|
|
61
|
+
clearInterval(this.timer);
|
|
62
|
+
this.timer = null;
|
|
63
|
+
}
|
|
64
|
+
this.notified.clear();
|
|
65
|
+
this.getKeys = null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run a scan now (also called by the interval timer).
|
|
69
|
+
* Returns warnings found in this scan.
|
|
70
|
+
*/
|
|
71
|
+
scan() {
|
|
72
|
+
if (!this.getKeys)
|
|
73
|
+
return [];
|
|
74
|
+
const keys = this.getKeys();
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const warnings = [];
|
|
77
|
+
for (const record of keys) {
|
|
78
|
+
// Skip keys without expiry, revoked, or already expired
|
|
79
|
+
if (!record.expiresAt)
|
|
80
|
+
continue;
|
|
81
|
+
if (!record.active)
|
|
82
|
+
continue;
|
|
83
|
+
const expiresMs = new Date(record.expiresAt).getTime();
|
|
84
|
+
if (isNaN(expiresMs))
|
|
85
|
+
continue;
|
|
86
|
+
const remainingMs = expiresMs - now;
|
|
87
|
+
if (remainingMs <= 0)
|
|
88
|
+
continue; // Already expired
|
|
89
|
+
const remainingSeconds = Math.round(remainingMs / 1000);
|
|
90
|
+
// Check each threshold (sorted descending)
|
|
91
|
+
for (const threshold of this.config.thresholds) {
|
|
92
|
+
if (remainingSeconds <= threshold) {
|
|
93
|
+
const dedupeKey = `${record.key.slice(0, 10)}:${threshold}`;
|
|
94
|
+
// Skip if already notified for this key+threshold
|
|
95
|
+
if (this.notified.has(dedupeKey))
|
|
96
|
+
continue;
|
|
97
|
+
const warning = {
|
|
98
|
+
key: record.key,
|
|
99
|
+
name: record.name,
|
|
100
|
+
alias: record.alias,
|
|
101
|
+
namespace: record.namespace,
|
|
102
|
+
expiresAt: record.expiresAt,
|
|
103
|
+
remainingSeconds,
|
|
104
|
+
remainingHuman: formatDuration(remainingSeconds),
|
|
105
|
+
thresholdSeconds: threshold,
|
|
106
|
+
};
|
|
107
|
+
warnings.push(warning);
|
|
108
|
+
this.notified.set(dedupeKey, now);
|
|
109
|
+
// Emit callback
|
|
110
|
+
if (this.onWarning) {
|
|
111
|
+
try {
|
|
112
|
+
this.onWarning(warning);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Swallow callback errors — scanner must not crash
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Only fire the most specific (smallest) threshold per key per scan
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Cleanup old de-duplication entries (older than 2x the largest threshold)
|
|
124
|
+
const maxThreshold = this.config.thresholds[0] || 0;
|
|
125
|
+
const cleanupCutoff = now - maxThreshold * 2 * 1000;
|
|
126
|
+
for (const [k, ts] of this.notified) {
|
|
127
|
+
if (ts < cleanupCutoff)
|
|
128
|
+
this.notified.delete(k);
|
|
129
|
+
}
|
|
130
|
+
return warnings;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Query keys expiring within a time window (for the admin endpoint).
|
|
134
|
+
* Does NOT trigger notifications — this is a read-only query.
|
|
135
|
+
*/
|
|
136
|
+
static queryExpiring(keys, withinSeconds) {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const results = [];
|
|
139
|
+
for (const record of keys) {
|
|
140
|
+
if (!record.expiresAt)
|
|
141
|
+
continue;
|
|
142
|
+
if (!record.active)
|
|
143
|
+
continue;
|
|
144
|
+
const expiresMs = new Date(record.expiresAt).getTime();
|
|
145
|
+
if (isNaN(expiresMs))
|
|
146
|
+
continue;
|
|
147
|
+
const remainingMs = expiresMs - now;
|
|
148
|
+
if (remainingMs <= 0)
|
|
149
|
+
continue; // Already expired
|
|
150
|
+
const remainingSeconds = Math.round(remainingMs / 1000);
|
|
151
|
+
if (remainingSeconds > withinSeconds)
|
|
152
|
+
continue;
|
|
153
|
+
results.push({
|
|
154
|
+
keyPrefix: record.key.slice(0, 10) + '...',
|
|
155
|
+
name: record.name,
|
|
156
|
+
alias: record.alias,
|
|
157
|
+
namespace: record.namespace,
|
|
158
|
+
expiresAt: record.expiresAt,
|
|
159
|
+
remainingSeconds,
|
|
160
|
+
remainingHuman: formatDuration(remainingSeconds),
|
|
161
|
+
suspended: record.suspended || false,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Sort by remaining time ascending (most urgent first)
|
|
165
|
+
results.sort((a, b) => a.remainingSeconds - b.remainingSeconds);
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get scanner status (for /health or diagnostics).
|
|
170
|
+
*/
|
|
171
|
+
get status() {
|
|
172
|
+
return {
|
|
173
|
+
enabled: this.config.enabled,
|
|
174
|
+
intervalSeconds: this.config.intervalSeconds,
|
|
175
|
+
thresholds: this.config.thresholds,
|
|
176
|
+
notifiedCount: this.notified.size,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Clear de-duplication state (for testing).
|
|
181
|
+
*/
|
|
182
|
+
clearNotified() {
|
|
183
|
+
this.notified.clear();
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Update config at runtime (for config hot-reload).
|
|
187
|
+
*/
|
|
188
|
+
updateConfig(config) {
|
|
189
|
+
if (config.intervalSeconds !== undefined) {
|
|
190
|
+
this.config.intervalSeconds = Math.max(60, config.intervalSeconds);
|
|
191
|
+
}
|
|
192
|
+
if (config.thresholds !== undefined) {
|
|
193
|
+
this.config.thresholds = [...config.thresholds].sort((a, b) => b - a);
|
|
194
|
+
}
|
|
195
|
+
if (config.enabled !== undefined) {
|
|
196
|
+
this.config.enabled = config.enabled;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.ExpiryScanner = ExpiryScanner;
|
|
201
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
202
|
+
function formatDuration(seconds) {
|
|
203
|
+
if (seconds < 60)
|
|
204
|
+
return `${seconds}s`;
|
|
205
|
+
if (seconds < 3600)
|
|
206
|
+
return `${Math.round(seconds / 60)}m`;
|
|
207
|
+
if (seconds < 86400) {
|
|
208
|
+
const hours = Math.floor(seconds / 3600);
|
|
209
|
+
const mins = Math.round((seconds % 3600) / 60);
|
|
210
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
211
|
+
}
|
|
212
|
+
const days = Math.floor(seconds / 86400);
|
|
213
|
+
const hours = Math.round((seconds % 86400) / 3600);
|
|
214
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=expiry-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry-scanner.js","sourceRoot":"","sources":["../src/expiry-scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { ScopedTokenManager } from './tokens';
|
|
|
30
30
|
import { AdminKeyManager } from './admin-keys';
|
|
31
31
|
import { PluginManager, PayGatePlugin } from './plugin';
|
|
32
32
|
import { KeyGroupManager } from './groups';
|
|
33
|
+
import { ExpiryScanner } from './expiry-scanner';
|
|
33
34
|
/** Union type for both proxy backends */
|
|
34
35
|
type ProxyBackend = McpProxy | HttpMcpProxy;
|
|
35
36
|
export declare class PayGateServer {
|
|
@@ -68,6 +69,8 @@ export declare class PayGateServer {
|
|
|
68
69
|
/** Plugin manager for extensible middleware hooks */
|
|
69
70
|
readonly plugins: PluginManager;
|
|
70
71
|
readonly groups: KeyGroupManager;
|
|
72
|
+
/** Background key expiry scanner */
|
|
73
|
+
readonly expiryScanner: ExpiryScanner;
|
|
71
74
|
/** Server start time (ms since epoch) */
|
|
72
75
|
private readonly startedAt;
|
|
73
76
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -148,6 +151,7 @@ export declare class PayGateServer {
|
|
|
148
151
|
private handleSetIpAllowlist;
|
|
149
152
|
private handleSearchKeysByTag;
|
|
150
153
|
private handleKeyUsage;
|
|
154
|
+
private handleKeysExpiring;
|
|
151
155
|
private handleSetAutoTopup;
|
|
152
156
|
private handleBalance;
|
|
153
157
|
private handleLimits;
|
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;AAU7F,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
|
@@ -83,6 +83,7 @@ const tokens_1 = require("./tokens");
|
|
|
83
83
|
const admin_keys_1 = require("./admin-keys");
|
|
84
84
|
const plugin_1 = require("./plugin");
|
|
85
85
|
const groups_1 = require("./groups");
|
|
86
|
+
const expiry_scanner_1 = require("./expiry-scanner");
|
|
86
87
|
/** Max request body size: 1MB */
|
|
87
88
|
const MAX_BODY_SIZE = 1_048_576;
|
|
88
89
|
class PayGateServer {
|
|
@@ -121,6 +122,8 @@ class PayGateServer {
|
|
|
121
122
|
/** Plugin manager for extensible middleware hooks */
|
|
122
123
|
plugins;
|
|
123
124
|
groups;
|
|
125
|
+
/** Background key expiry scanner */
|
|
126
|
+
expiryScanner;
|
|
124
127
|
/** Server start time (ms since epoch) */
|
|
125
128
|
startedAt = Date.now();
|
|
126
129
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -239,6 +242,24 @@ class PayGateServer {
|
|
|
239
242
|
this.metrics.registerGauge('paygate_groups_total', 'Number of active key groups', () => {
|
|
240
243
|
return this.groups.count;
|
|
241
244
|
});
|
|
245
|
+
// Key expiry scanner — proactive background scanning for expiring keys
|
|
246
|
+
const scannerConfig = this.config.expiryScanner;
|
|
247
|
+
this.expiryScanner = new expiry_scanner_1.ExpiryScanner(scannerConfig ? {
|
|
248
|
+
enabled: scannerConfig.enabled !== false,
|
|
249
|
+
intervalSeconds: scannerConfig.intervalSeconds || 3600,
|
|
250
|
+
thresholds: scannerConfig.thresholds || [604800, 86400, 3600],
|
|
251
|
+
} : undefined);
|
|
252
|
+
// Wire scanner callbacks: audit + webhook
|
|
253
|
+
this.expiryScanner.onWarning = (warning) => {
|
|
254
|
+
const keyMasked = (0, audit_1.maskKeyForAudit)(warning.key);
|
|
255
|
+
this.audit.log('key.expiry_warning', 'system', `Key "${warning.name}" expires in ${warning.remainingHuman} (threshold: ${warning.thresholdSeconds}s)`, { keyMasked, expiresAt: warning.expiresAt, remainingSeconds: warning.remainingSeconds, thresholdSeconds: warning.thresholdSeconds, alias: warning.alias || null });
|
|
256
|
+
this.emitWebhookAdmin('key.expiry_warning', 'system', {
|
|
257
|
+
keyMasked, keyName: warning.name, alias: warning.alias || null,
|
|
258
|
+
namespace: warning.namespace, expiresAt: warning.expiresAt,
|
|
259
|
+
remainingSeconds: warning.remainingSeconds, remainingHuman: warning.remainingHuman,
|
|
260
|
+
thresholdSeconds: warning.thresholdSeconds,
|
|
261
|
+
});
|
|
262
|
+
};
|
|
242
263
|
// Scoped token manager (uses bootstrap admin key as signing secret, padded to min length)
|
|
243
264
|
const tokenSecret = this.bootstrapAdminKey.length >= 8
|
|
244
265
|
? this.bootstrapAdminKey
|
|
@@ -298,6 +319,8 @@ class PayGateServer {
|
|
|
298
319
|
console.log('[paygate] Redis distributed state enabled');
|
|
299
320
|
}
|
|
300
321
|
await this.handler.start();
|
|
322
|
+
// Start the key expiry scanner (proactive background scanning)
|
|
323
|
+
this.expiryScanner.start(() => this.gate.store.getAllRecords());
|
|
301
324
|
// Plugin lifecycle: onStart
|
|
302
325
|
if (this.plugins.count > 0) {
|
|
303
326
|
await this.plugins.executeStart();
|
|
@@ -384,6 +407,8 @@ class PayGateServer {
|
|
|
384
407
|
return this.handleSetAutoTopup(req, res);
|
|
385
408
|
case '/keys/usage':
|
|
386
409
|
return this.handleKeyUsage(req, res);
|
|
410
|
+
case '/keys/expiring':
|
|
411
|
+
return this.handleKeysExpiring(req, res);
|
|
387
412
|
case '/topup':
|
|
388
413
|
return this.handleTopUp(req, res);
|
|
389
414
|
case '/keys/transfer':
|
|
@@ -908,6 +933,7 @@ class PayGateServer {
|
|
|
908
933
|
searchKeys: 'POST /keys/search — Search keys by tags (requires X-Admin-Key)',
|
|
909
934
|
autoTopup: 'POST /keys/auto-topup — Configure auto-topup for a key (requires X-Admin-Key)',
|
|
910
935
|
keyUsage: 'GET /keys/usage?key=... — Per-key usage breakdown (requires X-Admin-Key)',
|
|
936
|
+
keysExpiring: 'GET /keys/expiring?within=86400 — List keys expiring within N seconds (requires X-Admin-Key)',
|
|
911
937
|
pricing: 'GET /pricing — Tool pricing breakdown (public)',
|
|
912
938
|
mcpPayment: 'GET /.well-known/mcp-payment — Payment metadata (SEP-2007)',
|
|
913
939
|
audit: 'GET /audit — Query audit log (requires X-Admin-Key)',
|
|
@@ -2160,6 +2186,34 @@ class PayGateServer {
|
|
|
2160
2186
|
...usage,
|
|
2161
2187
|
}, null, 2));
|
|
2162
2188
|
}
|
|
2189
|
+
// ─── /keys/expiring — List keys expiring within a time window ───────────────
|
|
2190
|
+
handleKeysExpiring(req, res) {
|
|
2191
|
+
if (req.method !== 'GET') {
|
|
2192
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2193
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (!this.checkAdmin(req, res))
|
|
2197
|
+
return;
|
|
2198
|
+
const urlParts = req.url?.split('?') || [];
|
|
2199
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
2200
|
+
const withinStr = params.get('within');
|
|
2201
|
+
const within = withinStr ? parseInt(withinStr, 10) : 86400; // Default: 24 hours
|
|
2202
|
+
if (isNaN(within) || within <= 0) {
|
|
2203
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2204
|
+
res.end(JSON.stringify({ error: 'Invalid within parameter — must be a positive number of seconds' }));
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
const allKeys = this.gate.store.getAllRecords();
|
|
2208
|
+
const expiring = expiry_scanner_1.ExpiryScanner.queryExpiring(allKeys, within);
|
|
2209
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2210
|
+
res.end(JSON.stringify({
|
|
2211
|
+
within,
|
|
2212
|
+
count: expiring.length,
|
|
2213
|
+
scanner: this.expiryScanner.status,
|
|
2214
|
+
keys: expiring,
|
|
2215
|
+
}, null, 2));
|
|
2216
|
+
}
|
|
2163
2217
|
// ─── /keys/auto-topup — Configure auto-topup ────────────────────────────────
|
|
2164
2218
|
async handleSetAutoTopup(req, res) {
|
|
2165
2219
|
if (req.method !== 'POST') {
|
|
@@ -4264,6 +4318,7 @@ class PayGateServer {
|
|
|
4264
4318
|
this.sessions.destroy();
|
|
4265
4319
|
this.audit.destroy();
|
|
4266
4320
|
this.tokens.destroy();
|
|
4321
|
+
this.expiryScanner.destroy();
|
|
4267
4322
|
if (this.redisSync) {
|
|
4268
4323
|
await this.redisSync.destroy();
|
|
4269
4324
|
}
|
|
@@ -4319,6 +4374,7 @@ class PayGateServer {
|
|
|
4319
4374
|
this.sessions.destroy();
|
|
4320
4375
|
this.audit.destroy();
|
|
4321
4376
|
this.tokens.destroy();
|
|
4377
|
+
this.expiryScanner.destroy();
|
|
4322
4378
|
if (this.redisSync) {
|
|
4323
4379
|
await this.redisSync.destroy();
|
|
4324
4380
|
}
|