paygate-mcp 5.1.0 → 5.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 +100 -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/cli.d.ts +5 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +118 -40
- package/dist/cli.js.map +1 -1
- 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 +6 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +129 -13
- package/dist/server.js.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
|
@@ -31,6 +31,7 @@ import { AdminKeyManager } from './admin-keys';
|
|
|
31
31
|
import { PluginManager, PayGatePlugin } from './plugin';
|
|
32
32
|
import { KeyGroupManager } from './groups';
|
|
33
33
|
import { ExpiryScanner } from './expiry-scanner';
|
|
34
|
+
import { KeyTemplateManager } from './key-templates';
|
|
34
35
|
/** Union type for both proxy backends */
|
|
35
36
|
type ProxyBackend = McpProxy | HttpMcpProxy;
|
|
36
37
|
export declare class PayGateServer {
|
|
@@ -71,6 +72,8 @@ export declare class PayGateServer {
|
|
|
71
72
|
readonly groups: KeyGroupManager;
|
|
72
73
|
/** Background key expiry scanner */
|
|
73
74
|
readonly expiryScanner: ExpiryScanner;
|
|
75
|
+
/** Key template manager for reusable key presets */
|
|
76
|
+
readonly templates: KeyTemplateManager;
|
|
74
77
|
/** Server start time (ms since epoch) */
|
|
75
78
|
private readonly startedAt;
|
|
76
79
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -214,11 +217,9 @@ export declare class PayGateServer {
|
|
|
214
217
|
private handleListAdminKeys;
|
|
215
218
|
private handleCreateAdminKey;
|
|
216
219
|
private handleRevokeAdminKey;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
* Fire-and-forget: errors logged, never thrown.
|
|
221
|
-
*/
|
|
220
|
+
private handleListTemplates;
|
|
221
|
+
private handleCreateTemplate;
|
|
222
|
+
private handleDeleteTemplate;
|
|
222
223
|
/**
|
|
223
224
|
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
224
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;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
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;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAKrD,yCAAyC;AACzC,KAAK,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAa5C,qBAAa,aAAa;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,aAAa,CAAqC;IAC1D,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAQ;IAC5C,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAQ;IAC5C,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,yCAAyC;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAS;IACzB,wCAAwC;IACxC,OAAO,CAAC,QAAQ,CAAK;IACrB,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAuB;IAEzC,0DAA0D;IAC1D,OAAO,KAAK,OAAO,GAElB;gBAGC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,EAC1D,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,mBAAmB,CAAC,EAAE,MAAM,EAC5B,OAAO,CAAC,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,CAAC,EAAE,MAAM;IAqLnB;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIjC;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAK1B,KAAK,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;YAsC5C,aAAa;YAqNb,SAAS;IAqNvB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA+C1B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAyCrB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,UAAU;IA2FlB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,YAAY;YAyCN,eAAe;IAiH7B,OAAO,CAAC,cAAc;YAaR,WAAW;YAiEX,oBAAoB;YA8GpB,oBAAoB;IA4IlC,OAAO,CAAC,eAAe;YAoDT,eAAe;YAsEf,eAAe;YAsDf,gBAAgB;YAkEhB,eAAe;YAgEf,cAAc;YAuFd,cAAc;YAoEd,eAAe;YA0Df,YAAY;YAkDZ,eAAe;YAwDf,cAAc;YA+Dd,aAAa;YAsDb,oBAAoB;YAsDpB,qBAAqB;IAgCnC,OAAO,CAAC,cAAc;IA2CtB,OAAO,CAAC,kBAAkB;YAiCZ,kBAAkB;IAoFhC,OAAO,CAAC,aAAa;YAuDP,YAAY;IAkD1B,OAAO,CAAC,WAAW;YA+CL,mBAAmB;IAmCjC,OAAO,CAAC,eAAe;IAYvB,+EAA+E;IAC/E,OAAO,CAAC,mBAAmB;IAU3B,oEAAoE;YACtD,mBAAmB;IA4DjC,yDAAyD;YAC3C,oBAAoB;IAuFlC,yCAAyC;YAC3B,gBAAgB;IA8E9B,uDAAuD;YACzC,iBAAiB;IAiC/B,sEAAsE;IACtE,OAAO,CAAC,kBAAkB;IAqB1B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,eAAe;YAYT,qBAAqB;IAmDnC,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,sBAAsB;YAwBhB,mBAAmB;YAoDnB,kBAAkB;IA4IhC,OAAO,CAAC,kBAAkB;IA8B1B,OAAO,CAAC,gBAAgB;IA6CxB,OAAO,CAAC,kBAAkB;IAgC1B,OAAO,CAAC,mBAAmB;YAiCb,iBAAiB;IA6H/B,OAAO,CAAC,wBAAwB;YAclB,yBAAyB;YAsCzB,yBAAyB;YAiDzB,yBAAyB;IA4CvC,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,iBAAiB;IAgCzB,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,UAAU;IAiClB,OAAO,CAAC,eAAe;YAiBT,gBAAgB;YA4ChB,gBAAgB;YA6ChB,gBAAgB;YAsChB,mBAAmB;YAsDnB,mBAAmB;IA8CjC,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,oBAAoB;YAgBd,iBAAiB;YAyDjB,iBAAiB;IAiE/B,OAAO,CAAC,uBAAuB;IAyB/B,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;YAOV,iBAAiB;YA2CjB,iBAAiB;YAuDjB,iBAAiB;YAyCjB,sBAAsB;YAsDtB,wBAAwB;IAiDtC,OAAO,CAAC,mBAAmB;YAsBb,oBAAoB;YAwDpB,oBAAoB;IAwDlC,OAAO,CAAC,mBAAmB;YAQb,oBAAoB;YAsCpB,oBAAoB;IAuClC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,QAAQ;IAkBV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B;;;;;;;OAOG;IACG,YAAY,CAAC,SAAS,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CA6CtD"}
|
package/dist/server.js
CHANGED
|
@@ -84,6 +84,7 @@ const admin_keys_1 = require("./admin-keys");
|
|
|
84
84
|
const plugin_1 = require("./plugin");
|
|
85
85
|
const groups_1 = require("./groups");
|
|
86
86
|
const expiry_scanner_1 = require("./expiry-scanner");
|
|
87
|
+
const key_templates_1 = require("./key-templates");
|
|
87
88
|
/** Max request body size: 1MB */
|
|
88
89
|
const MAX_BODY_SIZE = 1_048_576;
|
|
89
90
|
class PayGateServer {
|
|
@@ -124,6 +125,8 @@ class PayGateServer {
|
|
|
124
125
|
groups;
|
|
125
126
|
/** Background key expiry scanner */
|
|
126
127
|
expiryScanner;
|
|
128
|
+
/** Key template manager for reusable key presets */
|
|
129
|
+
templates;
|
|
127
130
|
/** Server start time (ms since epoch) */
|
|
128
131
|
startedAt = Date.now();
|
|
129
132
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -260,6 +263,12 @@ class PayGateServer {
|
|
|
260
263
|
thresholdSeconds: warning.thresholdSeconds,
|
|
261
264
|
});
|
|
262
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
|
+
});
|
|
263
272
|
// Scoped token manager (uses bootstrap admin key as signing secret, padded to min length)
|
|
264
273
|
const tokenSecret = this.bootstrapAdminKey.length >= 8
|
|
265
274
|
? this.bootstrapAdminKey
|
|
@@ -409,6 +418,14 @@ class PayGateServer {
|
|
|
409
418
|
return this.handleKeyUsage(req, res);
|
|
410
419
|
case '/keys/expiring':
|
|
411
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);
|
|
412
429
|
case '/topup':
|
|
413
430
|
return this.handleTopUp(req, res);
|
|
414
431
|
case '/keys/transfer':
|
|
@@ -934,6 +951,8 @@ class PayGateServer {
|
|
|
934
951
|
autoTopup: 'POST /keys/auto-topup — Configure auto-topup for a key (requires X-Admin-Key)',
|
|
935
952
|
keyUsage: 'GET /keys/usage?key=... — Per-key usage breakdown (requires X-Admin-Key)',
|
|
936
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)',
|
|
937
956
|
pricing: 'GET /pricing — Tool pricing breakdown (public)',
|
|
938
957
|
mcpPayment: 'GET /.well-known/mcp-payment — Payment metadata (SEP-2007)',
|
|
939
958
|
audit: 'GET /audit — Query audit log (requires X-Admin-Key)',
|
|
@@ -1045,14 +1064,24 @@ class PayGateServer {
|
|
|
1045
1064
|
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
1046
1065
|
return;
|
|
1047
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
|
+
}
|
|
1048
1077
|
const name = String(params.name || 'unnamed').slice(0, 200);
|
|
1049
|
-
const credits = Math.max(0, Math.floor(Number(params.credits
|
|
1078
|
+
const credits = Math.max(0, Math.floor(Number(params.credits ?? tpl?.credits ?? 100)));
|
|
1050
1079
|
if (credits <= 0) {
|
|
1051
1080
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1052
1081
|
res.end(JSON.stringify({ error: 'Credits must be a positive integer' }));
|
|
1053
1082
|
return;
|
|
1054
1083
|
}
|
|
1055
|
-
// 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
|
|
1056
1085
|
let expiresAt = null;
|
|
1057
1086
|
if (params.expiresIn && Number(params.expiresIn) > 0) {
|
|
1058
1087
|
expiresAt = new Date(Date.now() + Number(params.expiresIn) * 1000).toISOString();
|
|
@@ -1060,7 +1089,10 @@ class PayGateServer {
|
|
|
1060
1089
|
else if (params.expiresAt) {
|
|
1061
1090
|
expiresAt = String(params.expiresAt);
|
|
1062
1091
|
}
|
|
1063
|
-
|
|
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
|
|
1064
1096
|
let quota = undefined;
|
|
1065
1097
|
if (params.quota) {
|
|
1066
1098
|
quota = {
|
|
@@ -1070,15 +1102,28 @@ class PayGateServer {
|
|
|
1070
1102
|
monthlyCreditLimit: Math.max(0, Math.floor(Number(params.quota.monthlyCreditLimit) || 0)),
|
|
1071
1103
|
};
|
|
1072
1104
|
}
|
|
1105
|
+
else if (tpl?.quota) {
|
|
1106
|
+
quota = { ...tpl.quota };
|
|
1107
|
+
}
|
|
1073
1108
|
const record = this.gate.store.createKey(name, credits, {
|
|
1074
|
-
allowedTools: params.allowedTools,
|
|
1075
|
-
deniedTools: params.deniedTools,
|
|
1109
|
+
allowedTools: params.allowedTools || (tpl ? [...tpl.allowedTools] : undefined),
|
|
1110
|
+
deniedTools: params.deniedTools || (tpl ? [...tpl.deniedTools] : undefined),
|
|
1076
1111
|
expiresAt,
|
|
1077
1112
|
quota,
|
|
1078
|
-
tags: params.tags,
|
|
1079
|
-
ipAllowlist: params.ipAllowlist,
|
|
1080
|
-
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,
|
|
1081
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
|
+
}
|
|
1082
1127
|
// Sync new key to Redis (if configured)
|
|
1083
1128
|
if (this.redisSync) {
|
|
1084
1129
|
this.redisSync.saveKey(record).catch(() => { });
|
|
@@ -4264,11 +4309,82 @@ class PayGateServer {
|
|
|
4264
4309
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4265
4310
|
res.end(JSON.stringify({ revoked: true }));
|
|
4266
4311
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
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
|
+
}
|
|
4272
4388
|
/**
|
|
4273
4389
|
* Route admin webhook events through the WebhookRouter (for filter rules) or direct emitter.
|
|
4274
4390
|
*/
|