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/dist/server.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ import { AlertEngine } from './alerts';
|
|
|
27
27
|
import { TeamManager } from './teams';
|
|
28
28
|
import { RedisSync } from './redis-sync';
|
|
29
29
|
import { ScopedTokenManager } from './tokens';
|
|
30
|
+
import { AdminKeyManager } from './admin-keys';
|
|
31
|
+
import { PluginManager, PayGatePlugin } from './plugin';
|
|
30
32
|
/** Union type for both proxy backends */
|
|
31
33
|
type ProxyBackend = McpProxy | HttpMcpProxy;
|
|
32
34
|
export declare class PayGateServer {
|
|
@@ -37,7 +39,10 @@ export declare class PayGateServer {
|
|
|
37
39
|
readonly router: MultiServerRouter | null;
|
|
38
40
|
private server;
|
|
39
41
|
private readonly config;
|
|
40
|
-
|
|
42
|
+
/** Admin key manager (multiple keys with role-based permissions) */
|
|
43
|
+
readonly adminKeys: AdminKeyManager;
|
|
44
|
+
/** The bootstrap admin key (from constructor or auto-generated) */
|
|
45
|
+
private readonly bootstrapAdminKey;
|
|
41
46
|
private stripeHandler;
|
|
42
47
|
/** OAuth 2.1 provider (null if OAuth is not enabled) */
|
|
43
48
|
readonly oauth: OAuthProvider | null;
|
|
@@ -59,6 +64,8 @@ export declare class PayGateServer {
|
|
|
59
64
|
readonly redisSync: RedisSync | null;
|
|
60
65
|
/** Scoped token manager for short-lived delegated tokens */
|
|
61
66
|
readonly tokens: ScopedTokenManager;
|
|
67
|
+
/** Plugin manager for extensible middleware hooks */
|
|
68
|
+
readonly plugins: PluginManager;
|
|
62
69
|
/** Server start time (ms since epoch) */
|
|
63
70
|
private readonly startedAt;
|
|
64
71
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -70,6 +77,19 @@ export declare class PayGateServer {
|
|
|
70
77
|
constructor(config: Partial<PayGateConfig> & {
|
|
71
78
|
serverCommand: string;
|
|
72
79
|
}, adminKey?: string, statePath?: string, remoteUrl?: string, stripeWebhookSecret?: string, servers?: ServerBackendConfig[], redisUrl?: string);
|
|
80
|
+
/**
|
|
81
|
+
* Register a plugin for extensible middleware hooks.
|
|
82
|
+
* Plugins run in registration order.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* server.use({
|
|
87
|
+
* name: 'custom-pricing',
|
|
88
|
+
* transformPrice: (tool, base) => tool.startsWith('premium_') ? base * 5 : null,
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
use(plugin: PayGatePlugin): this;
|
|
73
93
|
start(): Promise<{
|
|
74
94
|
port: number;
|
|
75
95
|
adminKey: string;
|
|
@@ -152,6 +172,10 @@ export declare class PayGateServer {
|
|
|
152
172
|
private handleCreateToken;
|
|
153
173
|
private handleRevokeToken;
|
|
154
174
|
private handleListRevokedTokens;
|
|
175
|
+
private handleListPlugins;
|
|
176
|
+
private handleListAdminKeys;
|
|
177
|
+
private handleCreateAdminKey;
|
|
178
|
+
private handleRevokeAdminKey;
|
|
155
179
|
/**
|
|
156
180
|
* Sync a key mutation to Redis. Call after any local KeyStore mutation
|
|
157
181
|
* (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
|
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;AAS7F,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;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -46,6 +46,8 @@ const teams_1 = require("./teams");
|
|
|
46
46
|
const redis_client_1 = require("./redis-client");
|
|
47
47
|
const redis_sync_1 = require("./redis-sync");
|
|
48
48
|
const tokens_1 = require("./tokens");
|
|
49
|
+
const admin_keys_1 = require("./admin-keys");
|
|
50
|
+
const plugin_1 = require("./plugin");
|
|
49
51
|
/** Max request body size: 1MB */
|
|
50
52
|
const MAX_BODY_SIZE = 1_048_576;
|
|
51
53
|
class PayGateServer {
|
|
@@ -56,7 +58,10 @@ class PayGateServer {
|
|
|
56
58
|
router;
|
|
57
59
|
server = null;
|
|
58
60
|
config;
|
|
59
|
-
|
|
61
|
+
/** Admin key manager (multiple keys with role-based permissions) */
|
|
62
|
+
adminKeys;
|
|
63
|
+
/** The bootstrap admin key (from constructor or auto-generated) */
|
|
64
|
+
bootstrapAdminKey;
|
|
60
65
|
stripeHandler = null;
|
|
61
66
|
/** OAuth 2.1 provider (null if OAuth is not enabled) */
|
|
62
67
|
oauth = null;
|
|
@@ -78,6 +83,8 @@ class PayGateServer {
|
|
|
78
83
|
redisSync = null;
|
|
79
84
|
/** Scoped token manager for short-lived delegated tokens */
|
|
80
85
|
tokens;
|
|
86
|
+
/** Plugin manager for extensible middleware hooks */
|
|
87
|
+
plugins;
|
|
81
88
|
/** Server start time (ms since epoch) */
|
|
82
89
|
startedAt = Date.now();
|
|
83
90
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -90,7 +97,11 @@ class PayGateServer {
|
|
|
90
97
|
}
|
|
91
98
|
constructor(config, adminKey, statePath, remoteUrl, stripeWebhookSecret, servers, redisUrl) {
|
|
92
99
|
this.config = { ...types_1.DEFAULT_CONFIG, ...config };
|
|
93
|
-
this.
|
|
100
|
+
this.bootstrapAdminKey = adminKey || `admin_${require('crypto').randomBytes(16).toString('hex')}`;
|
|
101
|
+
// Admin key manager with file persistence (separate from API key state)
|
|
102
|
+
const adminStatePath = statePath ? statePath.replace(/\.json$/, '-admin.json') : undefined;
|
|
103
|
+
this.adminKeys = new admin_keys_1.AdminKeyManager(adminStatePath);
|
|
104
|
+
this.adminKeys.bootstrap(this.bootstrapAdminKey);
|
|
94
105
|
this.gate = new gate_1.Gate(this.config, statePath);
|
|
95
106
|
// Multi-server mode: use Router
|
|
96
107
|
if (servers && servers.length > 0) {
|
|
@@ -136,6 +147,9 @@ class PayGateServer {
|
|
|
136
147
|
this.metrics.registerGauge('paygate_total_credits_available', 'Total credits across all active keys', () => {
|
|
137
148
|
return this.gate.store.listKeys().filter(k => k.active).reduce((sum, k) => sum + k.credits, 0);
|
|
138
149
|
});
|
|
150
|
+
this.metrics.registerGauge('paygate_admin_keys_total', 'Number of active admin keys', () => {
|
|
151
|
+
return this.adminKeys.activeCount;
|
|
152
|
+
});
|
|
139
153
|
// Analytics engine
|
|
140
154
|
this.analytics = new analytics_1.AnalyticsEngine();
|
|
141
155
|
// Alert engine
|
|
@@ -175,10 +189,16 @@ class PayGateServer {
|
|
|
175
189
|
this.redisSync.atomicTopup(apiKey, amount).catch(() => { });
|
|
176
190
|
}
|
|
177
191
|
};
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
192
|
+
// Plugin manager for extensible middleware hooks
|
|
193
|
+
this.plugins = new plugin_1.PluginManager();
|
|
194
|
+
this.gate.pluginManager = this.plugins;
|
|
195
|
+
this.metrics.registerGauge('paygate_plugins_total', 'Number of registered plugins', () => {
|
|
196
|
+
return this.plugins.count;
|
|
197
|
+
});
|
|
198
|
+
// Scoped token manager (uses bootstrap admin key as signing secret, padded to min length)
|
|
199
|
+
const tokenSecret = this.bootstrapAdminKey.length >= 8
|
|
200
|
+
? this.bootstrapAdminKey
|
|
201
|
+
: this.bootstrapAdminKey + require('crypto').randomBytes(8).toString('hex');
|
|
182
202
|
this.tokens = new tokens_1.ScopedTokenManager(tokenSecret);
|
|
183
203
|
// Redis distributed state (if configured)
|
|
184
204
|
if (redisUrl) {
|
|
@@ -201,6 +221,22 @@ class PayGateServer {
|
|
|
201
221
|
};
|
|
202
222
|
}
|
|
203
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Register a plugin for extensible middleware hooks.
|
|
226
|
+
* Plugins run in registration order.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* server.use({
|
|
231
|
+
* name: 'custom-pricing',
|
|
232
|
+
* transformPrice: (tool, base) => tool.startsWith('premium_') ? base * 5 : null,
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
use(plugin) {
|
|
237
|
+
this.plugins.register(plugin);
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
204
240
|
async start() {
|
|
205
241
|
// Initialize Redis sync before starting (loads state from Redis + starts pub/sub)
|
|
206
242
|
if (this.redisSync) {
|
|
@@ -209,6 +245,10 @@ class PayGateServer {
|
|
|
209
245
|
console.log('[paygate] Redis distributed state enabled');
|
|
210
246
|
}
|
|
211
247
|
await this.handler.start();
|
|
248
|
+
// Plugin lifecycle: onStart
|
|
249
|
+
if (this.plugins.count > 0) {
|
|
250
|
+
await this.plugins.executeStart();
|
|
251
|
+
}
|
|
212
252
|
return new Promise((resolve, reject) => {
|
|
213
253
|
this.server = (0, http_1.createServer)(async (req, res) => {
|
|
214
254
|
try {
|
|
@@ -222,7 +262,7 @@ class PayGateServer {
|
|
|
222
262
|
this.server.listen(this.config.port, () => {
|
|
223
263
|
const addr = this.server.address();
|
|
224
264
|
const actualPort = typeof addr === 'object' && addr ? addr.port : this.config.port;
|
|
225
|
-
resolve({ port: actualPort, adminKey: this.
|
|
265
|
+
resolve({ port: actualPort, adminKey: this.bootstrapAdminKey });
|
|
226
266
|
});
|
|
227
267
|
this.server.on('error', reject);
|
|
228
268
|
});
|
|
@@ -238,6 +278,12 @@ class PayGateServer {
|
|
|
238
278
|
res.end();
|
|
239
279
|
return;
|
|
240
280
|
}
|
|
281
|
+
// Plugin: onRequest — let plugins handle custom endpoints before routing
|
|
282
|
+
if (this.plugins.count > 0) {
|
|
283
|
+
const handled = await this.plugins.executeOnRequest(req, res);
|
|
284
|
+
if (handled)
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
241
287
|
const url = req.url?.split('?')[0] || '/';
|
|
242
288
|
switch (url) {
|
|
243
289
|
case '/mcp':
|
|
@@ -346,6 +392,18 @@ class PayGateServer {
|
|
|
346
392
|
return this.handleRevokeToken(req, res);
|
|
347
393
|
case '/tokens/revoked':
|
|
348
394
|
return this.handleListRevokedTokens(req, res);
|
|
395
|
+
// ─── Admin key management endpoints ──────────────────────────────
|
|
396
|
+
case '/admin/keys':
|
|
397
|
+
if (req.method === 'POST')
|
|
398
|
+
return this.handleCreateAdminKey(req, res);
|
|
399
|
+
if (req.method === 'GET')
|
|
400
|
+
return this.handleListAdminKeys(req, res);
|
|
401
|
+
break;
|
|
402
|
+
case '/admin/keys/revoke':
|
|
403
|
+
return this.handleRevokeAdminKey(req, res);
|
|
404
|
+
// ─── Plugin endpoints ──────────────────────────────────────────────
|
|
405
|
+
case '/plugins':
|
|
406
|
+
return this.handleListPlugins(req, res);
|
|
349
407
|
// ─── OAuth 2.1 endpoints ─────────────────────────────────────────
|
|
350
408
|
case '/.well-known/oauth-authorization-server':
|
|
351
409
|
return this.handleOAuthMetadata(req, res);
|
|
@@ -468,7 +526,22 @@ class PayGateServer {
|
|
|
468
526
|
}
|
|
469
527
|
return;
|
|
470
528
|
}
|
|
471
|
-
|
|
529
|
+
// Plugin: beforeToolCall — let plugins modify the request before forwarding
|
|
530
|
+
let pluginRequest = request;
|
|
531
|
+
if (this.plugins.count > 0 && request.method === 'tools/call') {
|
|
532
|
+
const toolName = request.params?.name || '';
|
|
533
|
+
const toolArgs = request.params?.arguments;
|
|
534
|
+
const pluginCtx = { apiKey, toolName, toolArgs, request };
|
|
535
|
+
pluginRequest = await this.plugins.executeBeforeToolCall(pluginCtx);
|
|
536
|
+
}
|
|
537
|
+
let response = await this.handler.handleRequest(pluginRequest, apiKey, clientIp, scopedTokenTools);
|
|
538
|
+
// Plugin: afterToolCall — let plugins modify the response
|
|
539
|
+
if (this.plugins.count > 0 && request.method === 'tools/call') {
|
|
540
|
+
const toolName = request.params?.name || '';
|
|
541
|
+
const toolArgs = request.params?.arguments;
|
|
542
|
+
const pluginCtx = { apiKey, toolName, toolArgs, request };
|
|
543
|
+
response = await this.plugins.executeAfterToolCall(pluginCtx, response);
|
|
544
|
+
}
|
|
472
545
|
// Inject pricing metadata into tools/list responses
|
|
473
546
|
if (request.method === 'tools/list' && response.result) {
|
|
474
547
|
const result = response.result;
|
|
@@ -737,6 +810,10 @@ class PayGateServer {
|
|
|
737
810
|
createToken: 'POST /tokens — Create scoped token (requires X-Admin-Key)',
|
|
738
811
|
revokeToken: 'POST /tokens/revoke — Revoke a scoped token (requires X-Admin-Key)',
|
|
739
812
|
listRevokedTokens: 'GET /tokens/revoked — List revoked tokens (requires X-Admin-Key)',
|
|
813
|
+
adminKeys: 'GET /admin/keys — List admin keys (requires X-Admin-Key, super_admin)',
|
|
814
|
+
createAdminKey: 'POST /admin/keys — Create admin key with role (requires X-Admin-Key, super_admin)',
|
|
815
|
+
revokeAdminKey: 'POST /admin/keys/revoke — Revoke an admin key (requires X-Admin-Key, super_admin)',
|
|
816
|
+
plugins: 'GET /plugins — List registered plugins (requires X-Admin-Key)',
|
|
740
817
|
...(this.oauth ? {
|
|
741
818
|
oauthMetadata: 'GET /.well-known/oauth-authorization-server — OAuth 2.1 server metadata',
|
|
742
819
|
oauthRegister: 'POST /oauth/register — Register OAuth client',
|
|
@@ -798,7 +875,7 @@ class PayGateServer {
|
|
|
798
875
|
}
|
|
799
876
|
// ─── /keys — Create ─────────────────────────────────────────────────────────
|
|
800
877
|
async handleCreateKey(req, res) {
|
|
801
|
-
if (!this.checkAdmin(req, res))
|
|
878
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
802
879
|
return;
|
|
803
880
|
const body = await this.readBody(req);
|
|
804
881
|
let params;
|
|
@@ -891,7 +968,7 @@ class PayGateServer {
|
|
|
891
968
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
892
969
|
return;
|
|
893
970
|
}
|
|
894
|
-
if (!this.checkAdmin(req, res))
|
|
971
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
895
972
|
return;
|
|
896
973
|
const body = await this.readBody(req);
|
|
897
974
|
let params;
|
|
@@ -946,7 +1023,7 @@ class PayGateServer {
|
|
|
946
1023
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
947
1024
|
return;
|
|
948
1025
|
}
|
|
949
|
-
if (!this.checkAdmin(req, res))
|
|
1026
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
950
1027
|
return;
|
|
951
1028
|
const body = await this.readBody(req);
|
|
952
1029
|
let params;
|
|
@@ -992,7 +1069,7 @@ class PayGateServer {
|
|
|
992
1069
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
993
1070
|
return;
|
|
994
1071
|
}
|
|
995
|
-
if (!this.checkAdmin(req, res))
|
|
1072
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
996
1073
|
return;
|
|
997
1074
|
const body = await this.readBody(req);
|
|
998
1075
|
let params;
|
|
@@ -1044,7 +1121,7 @@ class PayGateServer {
|
|
|
1044
1121
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1045
1122
|
return;
|
|
1046
1123
|
}
|
|
1047
|
-
if (!this.checkAdmin(req, res))
|
|
1124
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1048
1125
|
return;
|
|
1049
1126
|
const body = await this.readBody(req);
|
|
1050
1127
|
let params;
|
|
@@ -1088,7 +1165,7 @@ class PayGateServer {
|
|
|
1088
1165
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1089
1166
|
return;
|
|
1090
1167
|
}
|
|
1091
|
-
if (!this.checkAdmin(req, res))
|
|
1168
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1092
1169
|
return;
|
|
1093
1170
|
const body = await this.readBody(req);
|
|
1094
1171
|
let params;
|
|
@@ -1138,7 +1215,7 @@ class PayGateServer {
|
|
|
1138
1215
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1139
1216
|
return;
|
|
1140
1217
|
}
|
|
1141
|
-
if (!this.checkAdmin(req, res))
|
|
1218
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1142
1219
|
return;
|
|
1143
1220
|
const body = await this.readBody(req);
|
|
1144
1221
|
let params;
|
|
@@ -1194,7 +1271,7 @@ class PayGateServer {
|
|
|
1194
1271
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1195
1272
|
return;
|
|
1196
1273
|
}
|
|
1197
|
-
if (!this.checkAdmin(req, res))
|
|
1274
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1198
1275
|
return;
|
|
1199
1276
|
const body = await this.readBody(req);
|
|
1200
1277
|
let params;
|
|
@@ -1241,7 +1318,7 @@ class PayGateServer {
|
|
|
1241
1318
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1242
1319
|
return;
|
|
1243
1320
|
}
|
|
1244
|
-
if (!this.checkAdmin(req, res))
|
|
1321
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1245
1322
|
return;
|
|
1246
1323
|
const body = await this.readBody(req);
|
|
1247
1324
|
let params;
|
|
@@ -1316,7 +1393,7 @@ class PayGateServer {
|
|
|
1316
1393
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1317
1394
|
return;
|
|
1318
1395
|
}
|
|
1319
|
-
if (!this.checkAdmin(req, res))
|
|
1396
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1320
1397
|
return;
|
|
1321
1398
|
const body = await this.readBody(req);
|
|
1322
1399
|
let params;
|
|
@@ -1437,7 +1514,7 @@ class PayGateServer {
|
|
|
1437
1514
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
1438
1515
|
return;
|
|
1439
1516
|
}
|
|
1440
|
-
if (!this.checkAdmin(req, res))
|
|
1517
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1441
1518
|
return;
|
|
1442
1519
|
const body = await this.readBody(req);
|
|
1443
1520
|
let params;
|
|
@@ -1882,7 +1959,7 @@ class PayGateServer {
|
|
|
1882
1959
|
}));
|
|
1883
1960
|
}
|
|
1884
1961
|
async handleConfigureAlerts(req, res) {
|
|
1885
|
-
if (!this.checkAdmin(req, res))
|
|
1962
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1886
1963
|
return;
|
|
1887
1964
|
const body = await this.readBody(req);
|
|
1888
1965
|
let params;
|
|
@@ -1942,7 +2019,7 @@ class PayGateServer {
|
|
|
1942
2019
|
}));
|
|
1943
2020
|
}
|
|
1944
2021
|
handleClearDeadLetters(req, res) {
|
|
1945
|
-
if (!this.checkAdmin(req, res))
|
|
2022
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
1946
2023
|
return;
|
|
1947
2024
|
if (!this.gate.webhook) {
|
|
1948
2025
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -2048,9 +2125,10 @@ class PayGateServer {
|
|
|
2048
2125
|
res.end(JSON.stringify(this.audit.stats(), null, 2));
|
|
2049
2126
|
}
|
|
2050
2127
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
2051
|
-
checkAdmin(req, res) {
|
|
2128
|
+
checkAdmin(req, res, minRole) {
|
|
2052
2129
|
const adminKey = req.headers['x-admin-key'];
|
|
2053
|
-
|
|
2130
|
+
const record = adminKey ? this.adminKeys.validate(adminKey) : null;
|
|
2131
|
+
if (!record) {
|
|
2054
2132
|
this.audit.log('admin.auth_failed', 'unknown', `Admin auth failed on ${req.url}`, {
|
|
2055
2133
|
url: req.url,
|
|
2056
2134
|
method: req.method,
|
|
@@ -2059,6 +2137,18 @@ class PayGateServer {
|
|
|
2059
2137
|
res.end(JSON.stringify({ error: 'Invalid admin key' }));
|
|
2060
2138
|
return false;
|
|
2061
2139
|
}
|
|
2140
|
+
// Role-based permission check (if a minimum role is specified)
|
|
2141
|
+
if (minRole && admin_keys_1.ROLE_HIERARCHY[record.role] < admin_keys_1.ROLE_HIERARCHY[minRole]) {
|
|
2142
|
+
this.audit.log('admin.auth_failed', adminKey.slice(0, 7) + '...' + adminKey.slice(-4), `Insufficient role for ${req.url} (need ${minRole}, have ${record.role})`, {
|
|
2143
|
+
url: req.url,
|
|
2144
|
+
method: req.method,
|
|
2145
|
+
requiredRole: minRole,
|
|
2146
|
+
currentRole: record.role,
|
|
2147
|
+
});
|
|
2148
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
2149
|
+
res.end(JSON.stringify({ error: 'Insufficient permissions', requiredRole: minRole, currentRole: record.role }));
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2062
2152
|
return true;
|
|
2063
2153
|
}
|
|
2064
2154
|
// ─── /teams — Team management ────────────────────────────────────────────
|
|
@@ -2078,7 +2168,7 @@ class PayGateServer {
|
|
|
2078
2168
|
res.end(JSON.stringify({ teams, count: teams.length }));
|
|
2079
2169
|
}
|
|
2080
2170
|
async handleCreateTeam(req, res) {
|
|
2081
|
-
if (!this.checkAdmin(req, res))
|
|
2171
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2082
2172
|
return;
|
|
2083
2173
|
if (req.method !== 'POST') {
|
|
2084
2174
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -2117,7 +2207,7 @@ class PayGateServer {
|
|
|
2117
2207
|
res.end(JSON.stringify({ message: 'Team created', team }));
|
|
2118
2208
|
}
|
|
2119
2209
|
async handleUpdateTeam(req, res) {
|
|
2120
|
-
if (!this.checkAdmin(req, res))
|
|
2210
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2121
2211
|
return;
|
|
2122
2212
|
if (req.method !== 'POST') {
|
|
2123
2213
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -2157,7 +2247,7 @@ class PayGateServer {
|
|
|
2157
2247
|
res.end(JSON.stringify({ message: 'Team updated', team: this.teams.getTeam(params.teamId) }));
|
|
2158
2248
|
}
|
|
2159
2249
|
async handleDeleteTeam(req, res) {
|
|
2160
|
-
if (!this.checkAdmin(req, res))
|
|
2250
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2161
2251
|
return;
|
|
2162
2252
|
if (req.method !== 'POST') {
|
|
2163
2253
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -2191,7 +2281,7 @@ class PayGateServer {
|
|
|
2191
2281
|
res.end(JSON.stringify({ message: 'Team deleted' }));
|
|
2192
2282
|
}
|
|
2193
2283
|
async handleTeamAssignKey(req, res) {
|
|
2194
|
-
if (!this.checkAdmin(req, res))
|
|
2284
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2195
2285
|
return;
|
|
2196
2286
|
if (req.method !== 'POST') {
|
|
2197
2287
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -2240,7 +2330,7 @@ class PayGateServer {
|
|
|
2240
2330
|
res.end(JSON.stringify({ message: 'Key assigned to team' }));
|
|
2241
2331
|
}
|
|
2242
2332
|
async handleTeamRemoveKey(req, res) {
|
|
2243
|
-
if (!this.checkAdmin(req, res))
|
|
2333
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2244
2334
|
return;
|
|
2245
2335
|
if (req.method !== 'POST') {
|
|
2246
2336
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
@@ -2320,7 +2410,7 @@ class PayGateServer {
|
|
|
2320
2410
|
}
|
|
2321
2411
|
// ─── /tokens — Create scoped token ──────────────────────────────────────────
|
|
2322
2412
|
async handleCreateToken(req, res) {
|
|
2323
|
-
if (!this.checkAdmin(req, res))
|
|
2413
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2324
2414
|
return;
|
|
2325
2415
|
const body = await this.readBody(req);
|
|
2326
2416
|
let params;
|
|
@@ -2375,7 +2465,7 @@ class PayGateServer {
|
|
|
2375
2465
|
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
2376
2466
|
return;
|
|
2377
2467
|
}
|
|
2378
|
-
if (!this.checkAdmin(req, res))
|
|
2468
|
+
if (!this.checkAdmin(req, res, 'admin'))
|
|
2379
2469
|
return;
|
|
2380
2470
|
const body = await this.readBody(req);
|
|
2381
2471
|
let params;
|
|
@@ -2447,6 +2537,138 @@ class PayGateServer {
|
|
|
2447
2537
|
})),
|
|
2448
2538
|
}));
|
|
2449
2539
|
}
|
|
2540
|
+
// ─── /admin/keys — Admin key management ────────────────────────────────────
|
|
2541
|
+
// ─── GET /plugins — List registered plugins ─────────────────────────────
|
|
2542
|
+
handleListPlugins(req, res) {
|
|
2543
|
+
if (req.method !== 'GET') {
|
|
2544
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2545
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
if (!this.checkAdmin(req, res))
|
|
2549
|
+
return;
|
|
2550
|
+
const plugins = this.plugins.list();
|
|
2551
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2552
|
+
res.end(JSON.stringify({ count: plugins.length, plugins }));
|
|
2553
|
+
}
|
|
2554
|
+
// ─── Admin Key Management ────────────────────────────────────────────────
|
|
2555
|
+
handleListAdminKeys(req, res) {
|
|
2556
|
+
if (req.method !== 'GET') {
|
|
2557
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2558
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use GET.' }));
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
if (!this.checkAdmin(req, res, 'super_admin'))
|
|
2562
|
+
return;
|
|
2563
|
+
const keys = this.adminKeys.list().map(k => ({
|
|
2564
|
+
key: k.key.slice(0, 7) + '...' + k.key.slice(-4),
|
|
2565
|
+
name: k.name,
|
|
2566
|
+
role: k.role,
|
|
2567
|
+
createdAt: k.createdAt,
|
|
2568
|
+
createdBy: k.createdBy,
|
|
2569
|
+
active: k.active,
|
|
2570
|
+
lastUsedAt: k.lastUsedAt,
|
|
2571
|
+
}));
|
|
2572
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2573
|
+
res.end(JSON.stringify({ count: keys.length, keys }));
|
|
2574
|
+
}
|
|
2575
|
+
async handleCreateAdminKey(req, res) {
|
|
2576
|
+
if (req.method !== 'POST') {
|
|
2577
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2578
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
if (!this.checkAdmin(req, res, 'super_admin'))
|
|
2582
|
+
return;
|
|
2583
|
+
const body = await this.readBody(req);
|
|
2584
|
+
let params;
|
|
2585
|
+
try {
|
|
2586
|
+
params = JSON.parse(body);
|
|
2587
|
+
}
|
|
2588
|
+
catch {
|
|
2589
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2590
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
if (!params.name || typeof params.name !== 'string') {
|
|
2594
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2595
|
+
res.end(JSON.stringify({ error: 'Missing required field: name' }));
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const role = (params.role || 'admin');
|
|
2599
|
+
if (!['super_admin', 'admin', 'viewer'].includes(role)) {
|
|
2600
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2601
|
+
res.end(JSON.stringify({ error: 'Invalid role. Must be super_admin, admin, or viewer.' }));
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
// Mask the requesting admin key for audit
|
|
2605
|
+
const callerKey = req.headers['x-admin-key'];
|
|
2606
|
+
const callerMasked = callerKey.slice(0, 7) + '...' + callerKey.slice(-4);
|
|
2607
|
+
const record = this.adminKeys.create(params.name, role, callerMasked);
|
|
2608
|
+
this.audit.log('admin_key.created', callerMasked, `Created admin key "${params.name}" with role ${role}`, {
|
|
2609
|
+
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2610
|
+
role,
|
|
2611
|
+
});
|
|
2612
|
+
this.gate.webhook?.emitAdmin('admin_key.created', callerMasked, {
|
|
2613
|
+
newKeyMasked: record.key.slice(0, 7) + '...' + record.key.slice(-4),
|
|
2614
|
+
name: params.name,
|
|
2615
|
+
role,
|
|
2616
|
+
});
|
|
2617
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
2618
|
+
res.end(JSON.stringify({
|
|
2619
|
+
key: record.key,
|
|
2620
|
+
name: record.name,
|
|
2621
|
+
role: record.role,
|
|
2622
|
+
createdAt: record.createdAt,
|
|
2623
|
+
}));
|
|
2624
|
+
}
|
|
2625
|
+
async handleRevokeAdminKey(req, res) {
|
|
2626
|
+
if (req.method !== 'POST') {
|
|
2627
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2628
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
if (!this.checkAdmin(req, res, 'super_admin'))
|
|
2632
|
+
return;
|
|
2633
|
+
const body = await this.readBody(req);
|
|
2634
|
+
let params;
|
|
2635
|
+
try {
|
|
2636
|
+
params = JSON.parse(body);
|
|
2637
|
+
}
|
|
2638
|
+
catch {
|
|
2639
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2640
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
if (!params.key || typeof params.key !== 'string') {
|
|
2644
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2645
|
+
res.end(JSON.stringify({ error: 'Missing required field: key' }));
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const callerKey = req.headers['x-admin-key'];
|
|
2649
|
+
// Prevent revoking your own key
|
|
2650
|
+
if (params.key === callerKey) {
|
|
2651
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2652
|
+
res.end(JSON.stringify({ error: 'Cannot revoke your own admin key' }));
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
const result = this.adminKeys.revoke(params.key);
|
|
2656
|
+
if (!result.success) {
|
|
2657
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2658
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
const callerMasked = callerKey.slice(0, 7) + '...' + callerKey.slice(-4);
|
|
2662
|
+
const targetMasked = params.key.slice(0, 7) + '...' + params.key.slice(-4);
|
|
2663
|
+
this.audit.log('admin_key.revoked', callerMasked, `Revoked admin key ${targetMasked}`, {
|
|
2664
|
+
revokedKeyMasked: targetMasked,
|
|
2665
|
+
});
|
|
2666
|
+
this.gate.webhook?.emitAdmin('admin_key.revoked', callerMasked, {
|
|
2667
|
+
revokedKeyMasked: targetMasked,
|
|
2668
|
+
});
|
|
2669
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2670
|
+
res.end(JSON.stringify({ revoked: true }));
|
|
2671
|
+
}
|
|
2450
2672
|
/**
|
|
2451
2673
|
* Sync a key mutation to Redis. Call after any local KeyStore mutation
|
|
2452
2674
|
* (setAcl, setExpiry, setQuota, setTags, setIpAllowlist, setSpendingLimit).
|
|
@@ -2480,6 +2702,10 @@ class PayGateServer {
|
|
|
2480
2702
|
});
|
|
2481
2703
|
}
|
|
2482
2704
|
async stop() {
|
|
2705
|
+
// Plugin lifecycle: onStop (reverse order)
|
|
2706
|
+
if (this.plugins.count > 0) {
|
|
2707
|
+
await this.plugins.executeStop();
|
|
2708
|
+
}
|
|
2483
2709
|
await this.handler.stop();
|
|
2484
2710
|
this.gate.destroy();
|
|
2485
2711
|
this.oauth?.destroy();
|
|
@@ -2530,6 +2756,10 @@ class PayGateServer {
|
|
|
2530
2756
|
check();
|
|
2531
2757
|
});
|
|
2532
2758
|
console.log('[paygate] Drained — tearing down resources');
|
|
2759
|
+
// Plugin lifecycle: onStop (reverse order)
|
|
2760
|
+
if (this.plugins.count > 0) {
|
|
2761
|
+
await this.plugins.executeStop();
|
|
2762
|
+
}
|
|
2533
2763
|
// Tear down resources (but skip server.close, already closed above)
|
|
2534
2764
|
await this.handler.stop();
|
|
2535
2765
|
this.gate.destroy();
|