paygate-mcp 5.0.0 → 5.2.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 +94 -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/key-templates.d.ts +80 -0
- package/dist/key-templates.d.ts.map +1 -0
- package/dist/key-templates.js +129 -0
- package/dist/key-templates.js.map +1 -0
- package/dist/server.d.ts +10 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +185 -13
- 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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* KeyTemplateManager — Named templates for API key creation.
|
|
4
|
+
*
|
|
5
|
+
* Templates define reusable presets for key creation (credits, ACL, quotas, etc.).
|
|
6
|
+
* Instead of passing all options every time, use `template: "free-tier"` in POST /keys.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - CRUD: create, update, list, get, delete templates
|
|
10
|
+
* - File persistence (-templates.json alongside state file)
|
|
11
|
+
* - Templates define: credits, allowedTools, deniedTools, quota, ipAllowlist,
|
|
12
|
+
* spendingLimit, tags, namespace, expiryTtlSeconds, autoTopup
|
|
13
|
+
* - Max 100 templates
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.KeyTemplateManager = void 0;
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
// ─── Manager Class ────────────────────────────────────────────────────────────
|
|
20
|
+
const MAX_TEMPLATES = 100;
|
|
21
|
+
class KeyTemplateManager {
|
|
22
|
+
templates = new Map();
|
|
23
|
+
filePath;
|
|
24
|
+
constructor(filePath) {
|
|
25
|
+
this.filePath = filePath || null;
|
|
26
|
+
if (this.filePath) {
|
|
27
|
+
this.loadFromFile();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create or update a template.
|
|
32
|
+
*/
|
|
33
|
+
set(name, data) {
|
|
34
|
+
// Validate name
|
|
35
|
+
const sanitized = name.trim().slice(0, 50);
|
|
36
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(sanitized)) {
|
|
37
|
+
return { success: false, error: 'Template name must contain only letters, numbers, hyphens, and underscores' };
|
|
38
|
+
}
|
|
39
|
+
const existing = this.templates.get(sanitized);
|
|
40
|
+
const now = new Date().toISOString();
|
|
41
|
+
const template = {
|
|
42
|
+
name: sanitized,
|
|
43
|
+
description: String(data.description || existing?.description || '').slice(0, 500),
|
|
44
|
+
credits: Math.max(0, Math.floor(Number(data.credits ?? existing?.credits ?? 100))),
|
|
45
|
+
allowedTools: Array.isArray(data.allowedTools) ? data.allowedTools.filter(t => typeof t === 'string').slice(0, 100) : (existing?.allowedTools || []),
|
|
46
|
+
deniedTools: Array.isArray(data.deniedTools) ? data.deniedTools.filter(t => typeof t === 'string').slice(0, 100) : (existing?.deniedTools || []),
|
|
47
|
+
quota: data.quota !== undefined ? data.quota : existing?.quota,
|
|
48
|
+
ipAllowlist: Array.isArray(data.ipAllowlist) ? data.ipAllowlist.filter(t => typeof t === 'string').slice(0, 100) : (existing?.ipAllowlist || []),
|
|
49
|
+
spendingLimit: Math.max(0, Number(data.spendingLimit ?? existing?.spendingLimit ?? 0)),
|
|
50
|
+
tags: typeof data.tags === 'object' && data.tags !== null ? data.tags : (existing?.tags || {}),
|
|
51
|
+
namespace: String(data.namespace || existing?.namespace || 'default').trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 50) || 'default',
|
|
52
|
+
expiryTtlSeconds: Math.max(0, Math.floor(Number(data.expiryTtlSeconds ?? existing?.expiryTtlSeconds ?? 0))),
|
|
53
|
+
autoTopup: data.autoTopup !== undefined ? data.autoTopup : existing?.autoTopup,
|
|
54
|
+
createdAt: existing?.createdAt || now,
|
|
55
|
+
updatedAt: now,
|
|
56
|
+
};
|
|
57
|
+
// Check limit (only for new templates)
|
|
58
|
+
if (!existing && this.templates.size >= MAX_TEMPLATES) {
|
|
59
|
+
return { success: false, error: `Maximum ${MAX_TEMPLATES} templates reached` };
|
|
60
|
+
}
|
|
61
|
+
this.templates.set(sanitized, template);
|
|
62
|
+
this.saveToFile();
|
|
63
|
+
return { success: true, template };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get a template by name.
|
|
67
|
+
*/
|
|
68
|
+
get(name) {
|
|
69
|
+
return this.templates.get(name) || null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Delete a template.
|
|
73
|
+
*/
|
|
74
|
+
delete(name) {
|
|
75
|
+
const existed = this.templates.delete(name);
|
|
76
|
+
if (existed)
|
|
77
|
+
this.saveToFile();
|
|
78
|
+
return existed;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* List all templates.
|
|
82
|
+
*/
|
|
83
|
+
list() {
|
|
84
|
+
return Array.from(this.templates.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get template count.
|
|
88
|
+
*/
|
|
89
|
+
get count() {
|
|
90
|
+
return this.templates.size;
|
|
91
|
+
}
|
|
92
|
+
// ─── File Persistence ──────────────────────────────────────────────────────
|
|
93
|
+
saveToFile() {
|
|
94
|
+
if (!this.filePath)
|
|
95
|
+
return;
|
|
96
|
+
const data = Array.from(this.templates.entries());
|
|
97
|
+
const json = JSON.stringify(data, null, 2);
|
|
98
|
+
const tmpPath = this.filePath + '.tmp';
|
|
99
|
+
try {
|
|
100
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(this.filePath), { recursive: true });
|
|
101
|
+
(0, fs_1.writeFileSync)(tmpPath, json, 'utf-8');
|
|
102
|
+
(0, fs_1.renameSync)(tmpPath, this.filePath);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error(`[paygate] Failed to save templates: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
loadFromFile() {
|
|
109
|
+
if (!this.filePath || !(0, fs_1.existsSync)(this.filePath))
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
const json = (0, fs_1.readFileSync)(this.filePath, 'utf-8');
|
|
113
|
+
const data = JSON.parse(json);
|
|
114
|
+
if (!Array.isArray(data))
|
|
115
|
+
return;
|
|
116
|
+
for (const [name, template] of data) {
|
|
117
|
+
if (name && template && typeof template.name === 'string') {
|
|
118
|
+
this.templates.set(name, template);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log(`[paygate] Loaded ${this.templates.size} template(s) from ${this.filePath}`);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error(`[paygate] Failed to load templates: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.KeyTemplateManager = KeyTemplateManager;
|
|
129
|
+
//# sourceMappingURL=key-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-templates.js","sourceRoot":"","sources":["../src/key-templates.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ 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';
|
|
34
|
+
import { KeyTemplateManager } from './key-templates';
|
|
33
35
|
/** Union type for both proxy backends */
|
|
34
36
|
type ProxyBackend = McpProxy | HttpMcpProxy;
|
|
35
37
|
export declare class PayGateServer {
|
|
@@ -68,6 +70,10 @@ export declare class PayGateServer {
|
|
|
68
70
|
/** Plugin manager for extensible middleware hooks */
|
|
69
71
|
readonly plugins: PluginManager;
|
|
70
72
|
readonly groups: KeyGroupManager;
|
|
73
|
+
/** Background key expiry scanner */
|
|
74
|
+
readonly expiryScanner: ExpiryScanner;
|
|
75
|
+
/** Key template manager for reusable key presets */
|
|
76
|
+
readonly templates: KeyTemplateManager;
|
|
71
77
|
/** Server start time (ms since epoch) */
|
|
72
78
|
private readonly startedAt;
|
|
73
79
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -148,6 +154,7 @@ export declare class PayGateServer {
|
|
|
148
154
|
private handleSetIpAllowlist;
|
|
149
155
|
private handleSearchKeysByTag;
|
|
150
156
|
private handleKeyUsage;
|
|
157
|
+
private handleKeysExpiring;
|
|
151
158
|
private handleSetAutoTopup;
|
|
152
159
|
private handleBalance;
|
|
153
160
|
private handleLimits;
|
|
@@ -210,11 +217,9 @@ export declare class PayGateServer {
|
|
|
210
217
|
private handleListAdminKeys;
|
|
211
218
|
private handleCreateAdminKey;
|
|
212
219
|
private handleRevokeAdminKey;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* Fire-and-forget: errors logged, never thrown.
|
|
217
|
-
*/
|
|
220
|
+
private handleListTemplates;
|
|
221
|
+
private handleCreateTemplate;
|
|
222
|
+
private handleDeleteTemplate;
|
|
218
223
|
/**
|
|
219
224
|
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
220
225
|
*/
|
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,8 @@ 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");
|
|
87
|
+
const key_templates_1 = require("./key-templates");
|
|
86
88
|
/** Max request body size: 1MB */
|
|
87
89
|
const MAX_BODY_SIZE = 1_048_576;
|
|
88
90
|
class PayGateServer {
|
|
@@ -121,6 +123,10 @@ class PayGateServer {
|
|
|
121
123
|
/** Plugin manager for extensible middleware hooks */
|
|
122
124
|
plugins;
|
|
123
125
|
groups;
|
|
126
|
+
/** Background key expiry scanner */
|
|
127
|
+
expiryScanner;
|
|
128
|
+
/** Key template manager for reusable key presets */
|
|
129
|
+
templates;
|
|
124
130
|
/** Server start time (ms since epoch) */
|
|
125
131
|
startedAt = Date.now();
|
|
126
132
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -239,6 +245,30 @@ class PayGateServer {
|
|
|
239
245
|
this.metrics.registerGauge('paygate_groups_total', 'Number of active key groups', () => {
|
|
240
246
|
return this.groups.count;
|
|
241
247
|
});
|
|
248
|
+
// Key expiry scanner — proactive background scanning for expiring keys
|
|
249
|
+
const scannerConfig = this.config.expiryScanner;
|
|
250
|
+
this.expiryScanner = new expiry_scanner_1.ExpiryScanner(scannerConfig ? {
|
|
251
|
+
enabled: scannerConfig.enabled !== false,
|
|
252
|
+
intervalSeconds: scannerConfig.intervalSeconds || 3600,
|
|
253
|
+
thresholds: scannerConfig.thresholds || [604800, 86400, 3600],
|
|
254
|
+
} : undefined);
|
|
255
|
+
// Wire scanner callbacks: audit + webhook
|
|
256
|
+
this.expiryScanner.onWarning = (warning) => {
|
|
257
|
+
const keyMasked = (0, audit_1.maskKeyForAudit)(warning.key);
|
|
258
|
+
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 });
|
|
259
|
+
this.emitWebhookAdmin('key.expiry_warning', 'system', {
|
|
260
|
+
keyMasked, keyName: warning.name, alias: warning.alias || null,
|
|
261
|
+
namespace: warning.namespace, expiresAt: warning.expiresAt,
|
|
262
|
+
remainingSeconds: warning.remainingSeconds, remainingHuman: warning.remainingHuman,
|
|
263
|
+
thresholdSeconds: warning.thresholdSeconds,
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
// Key template manager for reusable key creation presets
|
|
267
|
+
const templatesStatePath = statePath ? statePath.replace(/\.json$/, '-templates.json') : undefined;
|
|
268
|
+
this.templates = new key_templates_1.KeyTemplateManager(templatesStatePath);
|
|
269
|
+
this.metrics.registerGauge('paygate_templates_total', 'Number of key templates', () => {
|
|
270
|
+
return this.templates.count;
|
|
271
|
+
});
|
|
242
272
|
// Scoped token manager (uses bootstrap admin key as signing secret, padded to min length)
|
|
243
273
|
const tokenSecret = this.bootstrapAdminKey.length >= 8
|
|
244
274
|
? this.bootstrapAdminKey
|
|
@@ -298,6 +328,8 @@ class PayGateServer {
|
|
|
298
328
|
console.log('[paygate] Redis distributed state enabled');
|
|
299
329
|
}
|
|
300
330
|
await this.handler.start();
|
|
331
|
+
// Start the key expiry scanner (proactive background scanning)
|
|
332
|
+
this.expiryScanner.start(() => this.gate.store.getAllRecords());
|
|
301
333
|
// Plugin lifecycle: onStart
|
|
302
334
|
if (this.plugins.count > 0) {
|
|
303
335
|
await this.plugins.executeStart();
|
|
@@ -384,6 +416,16 @@ class PayGateServer {
|
|
|
384
416
|
return this.handleSetAutoTopup(req, res);
|
|
385
417
|
case '/keys/usage':
|
|
386
418
|
return this.handleKeyUsage(req, res);
|
|
419
|
+
case '/keys/expiring':
|
|
420
|
+
return this.handleKeysExpiring(req, res);
|
|
421
|
+
case '/keys/templates':
|
|
422
|
+
if (req.method === 'GET')
|
|
423
|
+
return this.handleListTemplates(req, res);
|
|
424
|
+
if (req.method === 'POST')
|
|
425
|
+
return this.handleCreateTemplate(req, res);
|
|
426
|
+
break;
|
|
427
|
+
case '/keys/templates/delete':
|
|
428
|
+
return this.handleDeleteTemplate(req, res);
|
|
387
429
|
case '/topup':
|
|
388
430
|
return this.handleTopUp(req, res);
|
|
389
431
|
case '/keys/transfer':
|
|
@@ -908,6 +950,9 @@ class PayGateServer {
|
|
|
908
950
|
searchKeys: 'POST /keys/search — Search keys by tags (requires X-Admin-Key)',
|
|
909
951
|
autoTopup: 'POST /keys/auto-topup — Configure auto-topup for a key (requires X-Admin-Key)',
|
|
910
952
|
keyUsage: 'GET /keys/usage?key=... — Per-key usage breakdown (requires X-Admin-Key)',
|
|
953
|
+
keysExpiring: 'GET /keys/expiring?within=86400 — List keys expiring within N seconds (requires X-Admin-Key)',
|
|
954
|
+
keyTemplates: 'GET /keys/templates — List key templates + POST to create/update (requires X-Admin-Key)',
|
|
955
|
+
deleteTemplate: 'POST /keys/templates/delete — Delete a key template (requires X-Admin-Key)',
|
|
911
956
|
pricing: 'GET /pricing — Tool pricing breakdown (public)',
|
|
912
957
|
mcpPayment: 'GET /.well-known/mcp-payment — Payment metadata (SEP-2007)',
|
|
913
958
|
audit: 'GET /audit — Query audit log (requires X-Admin-Key)',
|
|
@@ -1019,14 +1064,24 @@ class PayGateServer {
|
|
|
1019
1064
|
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
1020
1065
|
return;
|
|
1021
1066
|
}
|
|
1067
|
+
// Resolve template defaults (explicit params override template values)
|
|
1068
|
+
let tpl = null;
|
|
1069
|
+
if (params.template) {
|
|
1070
|
+
tpl = this.templates.get(params.template);
|
|
1071
|
+
if (!tpl) {
|
|
1072
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1073
|
+
res.end(JSON.stringify({ error: `Template "${params.template}" not found` }));
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1022
1077
|
const name = String(params.name || 'unnamed').slice(0, 200);
|
|
1023
|
-
const credits = Math.max(0, Math.floor(Number(params.credits
|
|
1078
|
+
const credits = Math.max(0, Math.floor(Number(params.credits ?? tpl?.credits ?? 100)));
|
|
1024
1079
|
if (credits <= 0) {
|
|
1025
1080
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1026
1081
|
res.end(JSON.stringify({ error: 'Credits must be a positive integer' }));
|
|
1027
1082
|
return;
|
|
1028
1083
|
}
|
|
1029
|
-
// Calculate expiry: expiresIn (seconds) takes priority over expiresAt (ISO date)
|
|
1084
|
+
// Calculate expiry: expiresIn (seconds) takes priority over expiresAt (ISO date), template TTL is fallback
|
|
1030
1085
|
let expiresAt = null;
|
|
1031
1086
|
if (params.expiresIn && Number(params.expiresIn) > 0) {
|
|
1032
1087
|
expiresAt = new Date(Date.now() + Number(params.expiresIn) * 1000).toISOString();
|
|
@@ -1034,7 +1089,10 @@ class PayGateServer {
|
|
|
1034
1089
|
else if (params.expiresAt) {
|
|
1035
1090
|
expiresAt = String(params.expiresAt);
|
|
1036
1091
|
}
|
|
1037
|
-
|
|
1092
|
+
else if (tpl && tpl.expiryTtlSeconds > 0) {
|
|
1093
|
+
expiresAt = new Date(Date.now() + tpl.expiryTtlSeconds * 1000).toISOString();
|
|
1094
|
+
}
|
|
1095
|
+
// Parse quota: explicit params > template > undefined
|
|
1038
1096
|
let quota = undefined;
|
|
1039
1097
|
if (params.quota) {
|
|
1040
1098
|
quota = {
|
|
@@ -1044,15 +1102,28 @@ class PayGateServer {
|
|
|
1044
1102
|
monthlyCreditLimit: Math.max(0, Math.floor(Number(params.quota.monthlyCreditLimit) || 0)),
|
|
1045
1103
|
};
|
|
1046
1104
|
}
|
|
1105
|
+
else if (tpl?.quota) {
|
|
1106
|
+
quota = { ...tpl.quota };
|
|
1107
|
+
}
|
|
1047
1108
|
const record = this.gate.store.createKey(name, credits, {
|
|
1048
|
-
allowedTools: params.allowedTools,
|
|
1049
|
-
deniedTools: params.deniedTools,
|
|
1109
|
+
allowedTools: params.allowedTools || (tpl ? [...tpl.allowedTools] : undefined),
|
|
1110
|
+
deniedTools: params.deniedTools || (tpl ? [...tpl.deniedTools] : undefined),
|
|
1050
1111
|
expiresAt,
|
|
1051
1112
|
quota,
|
|
1052
|
-
tags: params.tags,
|
|
1053
|
-
ipAllowlist: params.ipAllowlist,
|
|
1054
|
-
namespace: params.namespace,
|
|
1113
|
+
tags: params.tags || (tpl ? { ...tpl.tags } : undefined),
|
|
1114
|
+
ipAllowlist: params.ipAllowlist || (tpl ? [...tpl.ipAllowlist] : undefined),
|
|
1115
|
+
namespace: params.namespace || tpl?.namespace,
|
|
1055
1116
|
});
|
|
1117
|
+
// Apply template spending limit if not explicitly set
|
|
1118
|
+
if (tpl && tpl.spendingLimit > 0 && record.spendingLimit === 0) {
|
|
1119
|
+
record.spendingLimit = tpl.spendingLimit;
|
|
1120
|
+
this.gate.store.save();
|
|
1121
|
+
}
|
|
1122
|
+
// Apply template auto-topup if not explicitly configured
|
|
1123
|
+
if (tpl?.autoTopup && !record.autoTopup) {
|
|
1124
|
+
record.autoTopup = { ...tpl.autoTopup };
|
|
1125
|
+
this.gate.store.save();
|
|
1126
|
+
}
|
|
1056
1127
|
// Sync new key to Redis (if configured)
|
|
1057
1128
|
if (this.redisSync) {
|
|
1058
1129
|
this.redisSync.saveKey(record).catch(() => { });
|
|
@@ -2160,6 +2231,34 @@ class PayGateServer {
|
|
|
2160
2231
|
...usage,
|
|
2161
2232
|
}, null, 2));
|
|
2162
2233
|
}
|
|
2234
|
+
// ─── /keys/expiring — List keys expiring within a time window ───────────────
|
|
2235
|
+
handleKeysExpiring(req, res) {
|
|
2236
|
+
if (req.method !== 'GET') {
|
|
2237
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2238
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
if (!this.checkAdmin(req, res))
|
|
2242
|
+
return;
|
|
2243
|
+
const urlParts = req.url?.split('?') || [];
|
|
2244
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
2245
|
+
const withinStr = params.get('within');
|
|
2246
|
+
const within = withinStr ? parseInt(withinStr, 10) : 86400; // Default: 24 hours
|
|
2247
|
+
if (isNaN(within) || within <= 0) {
|
|
2248
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2249
|
+
res.end(JSON.stringify({ error: 'Invalid within parameter — must be a positive number of seconds' }));
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
const allKeys = this.gate.store.getAllRecords();
|
|
2253
|
+
const expiring = expiry_scanner_1.ExpiryScanner.queryExpiring(allKeys, within);
|
|
2254
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2255
|
+
res.end(JSON.stringify({
|
|
2256
|
+
within,
|
|
2257
|
+
count: expiring.length,
|
|
2258
|
+
scanner: this.expiryScanner.status,
|
|
2259
|
+
keys: expiring,
|
|
2260
|
+
}, null, 2));
|
|
2261
|
+
}
|
|
2163
2262
|
// ─── /keys/auto-topup — Configure auto-topup ────────────────────────────────
|
|
2164
2263
|
async handleSetAutoTopup(req, res) {
|
|
2165
2264
|
if (req.method !== 'POST') {
|
|
@@ -4210,11 +4309,82 @@ class PayGateServer {
|
|
|
4210
4309
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4211
4310
|
res.end(JSON.stringify({ revoked: true }));
|
|
4212
4311
|
}
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4312
|
+
// ─── /keys/templates — CRUD ────────────────────────────────────────────────
|
|
4313
|
+
handleListTemplates(req, res) {
|
|
4314
|
+
if (!this.checkAdmin(req, res))
|
|
4315
|
+
return;
|
|
4316
|
+
const templates = this.templates.list();
|
|
4317
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4318
|
+
res.end(JSON.stringify({ total: templates.length, templates }));
|
|
4319
|
+
}
|
|
4320
|
+
async handleCreateTemplate(req, res) {
|
|
4321
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
4322
|
+
return;
|
|
4323
|
+
const body = await this.readBody(req);
|
|
4324
|
+
let params;
|
|
4325
|
+
try {
|
|
4326
|
+
params = JSON.parse(body);
|
|
4327
|
+
}
|
|
4328
|
+
catch {
|
|
4329
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4330
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
4331
|
+
return;
|
|
4332
|
+
}
|
|
4333
|
+
const name = params.name;
|
|
4334
|
+
if (!name || typeof name !== 'string') {
|
|
4335
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4336
|
+
res.end(JSON.stringify({ error: 'Missing required field: name' }));
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
const existing = this.templates.get(name);
|
|
4340
|
+
const result = this.templates.set(name, params);
|
|
4341
|
+
if (!result.success) {
|
|
4342
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4343
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
const eventType = existing ? 'template.updated' : 'template.created';
|
|
4347
|
+
this.audit.log(eventType, 'admin', `${existing ? 'Updated' : 'Created'} template: ${name}`, {
|
|
4348
|
+
templateName: name,
|
|
4349
|
+
});
|
|
4350
|
+
res.writeHead(existing ? 200 : 201, { 'Content-Type': 'application/json' });
|
|
4351
|
+
res.end(JSON.stringify({ template: result.template }));
|
|
4352
|
+
}
|
|
4353
|
+
async handleDeleteTemplate(req, res) {
|
|
4354
|
+
if (req.method !== 'POST') {
|
|
4355
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
4356
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
4357
|
+
return;
|
|
4358
|
+
}
|
|
4359
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
4360
|
+
return;
|
|
4361
|
+
const body = await this.readBody(req);
|
|
4362
|
+
let params;
|
|
4363
|
+
try {
|
|
4364
|
+
params = JSON.parse(body);
|
|
4365
|
+
}
|
|
4366
|
+
catch {
|
|
4367
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4368
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
if (!params.name || typeof params.name !== 'string') {
|
|
4372
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
4373
|
+
res.end(JSON.stringify({ error: 'Missing required field: name' }));
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
const deleted = this.templates.delete(params.name);
|
|
4377
|
+
if (!deleted) {
|
|
4378
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
4379
|
+
res.end(JSON.stringify({ error: `Template "${params.name}" not found` }));
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
this.audit.log('template.deleted', 'admin', `Deleted template: ${params.name}`, {
|
|
4383
|
+
templateName: params.name,
|
|
4384
|
+
});
|
|
4385
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4386
|
+
res.end(JSON.stringify({ deleted: true, name: params.name }));
|
|
4387
|
+
}
|
|
4218
4388
|
/**
|
|
4219
4389
|
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
4220
4390
|
*/
|
|
@@ -4264,6 +4434,7 @@ class PayGateServer {
|
|
|
4264
4434
|
this.sessions.destroy();
|
|
4265
4435
|
this.audit.destroy();
|
|
4266
4436
|
this.tokens.destroy();
|
|
4437
|
+
this.expiryScanner.destroy();
|
|
4267
4438
|
if (this.redisSync) {
|
|
4268
4439
|
await this.redisSync.destroy();
|
|
4269
4440
|
}
|
|
@@ -4319,6 +4490,7 @@ class PayGateServer {
|
|
|
4319
4490
|
this.sessions.destroy();
|
|
4320
4491
|
this.audit.destroy();
|
|
4321
4492
|
this.tokens.destroy();
|
|
4493
|
+
this.expiryScanner.destroy();
|
|
4322
4494
|
if (this.redisSync) {
|
|
4323
4495
|
await this.redisSync.destroy();
|
|
4324
4496
|
}
|