paygate-mcp 8.91.0 → 8.93.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/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +105 -17
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -98,6 +98,8 @@ export declare class PayGateServer {
|
|
|
98
98
|
readonly creditLedger: CreditLedger;
|
|
99
99
|
/** Rate limiter for admin API endpoints (brute-force protection) */
|
|
100
100
|
private readonly adminRateLimiter;
|
|
101
|
+
/** Rate limiter for session creation (prevents session slot exhaustion) */
|
|
102
|
+
private readonly sessionRateLimiter;
|
|
101
103
|
/** Server start time (ms since epoch) */
|
|
102
104
|
private readonly startedAt;
|
|
103
105
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -380,6 +382,11 @@ export declare class PayGateServer {
|
|
|
380
382
|
private syncKeyMutation;
|
|
381
383
|
/** Resolve the CORS origin based on config and incoming request Origin header */
|
|
382
384
|
private resolveCorsOrigin;
|
|
385
|
+
/**
|
|
386
|
+
* Check Content-Type is JSON. Returns true if valid, false and sends 415 if not.
|
|
387
|
+
* Exempt paths (like /oauth/token) accept form-urlencoded per RFC 6749.
|
|
388
|
+
*/
|
|
389
|
+
private requireJsonContentType;
|
|
383
390
|
private readBody;
|
|
384
391
|
stop(): Promise<void>;
|
|
385
392
|
/**
|
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;AAEH,OAAO,EAAgB,eAAe,EAA0B,MAAM,MAAM,CAAC;AAI7E,OAAO,EAAE,aAAa,EAAkB,mBAAmB,EAAkB,MAAM,SAAS,CAAC;AAE7F,OAAO,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAC;AASjE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,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,iBAAiB,CAAC;AAC/C,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;AAG3C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -141,7 +141,9 @@ function clampArray(arr, maxLen) {
|
|
|
141
141
|
* The full error is returned for internal logging only.
|
|
142
142
|
*/
|
|
143
143
|
function safeErrorMessage(err, fallback = 'Invalid request') {
|
|
144
|
-
const
|
|
144
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
145
|
+
// Truncate before regex matching to prevent ReDoS on crafted long strings
|
|
146
|
+
const msg = raw.slice(0, 500);
|
|
145
147
|
// Allow known-safe, controlled error messages to pass through.
|
|
146
148
|
// These are validation messages from our own code, not system/library errors.
|
|
147
149
|
const safePatterns = [
|
|
@@ -177,6 +179,18 @@ function sanitizeString(value, maxLen = MAX_STRING_FIELD) {
|
|
|
177
179
|
return '';
|
|
178
180
|
return String(value).slice(0, maxLen);
|
|
179
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Sanitize a request URL for safe inclusion in log output.
|
|
184
|
+
* Strips control characters (newlines, tabs, carriage returns, etc.) that could
|
|
185
|
+
* be used for log injection attacks (forging log entries, hiding malicious activity).
|
|
186
|
+
* Truncates to 2048 chars to prevent log bloat from absurdly long URLs.
|
|
187
|
+
*/
|
|
188
|
+
function sanitizeLogUrl(url) {
|
|
189
|
+
if (!url)
|
|
190
|
+
return '/';
|
|
191
|
+
// eslint-disable-next-line no-control-regex
|
|
192
|
+
return url.replace(/[\x00-\x1f\x7f]/g, '').slice(0, 2048);
|
|
193
|
+
}
|
|
180
194
|
/** Clamp a numeric value to [min, max], returning defaultVal for NaN/undefined. */
|
|
181
195
|
function clampInt(value, min, max, defaultVal) {
|
|
182
196
|
if (value === undefined || !Number.isFinite(value))
|
|
@@ -306,6 +320,8 @@ class PayGateServer {
|
|
|
306
320
|
creditLedger;
|
|
307
321
|
/** Rate limiter for admin API endpoints (brute-force protection) */
|
|
308
322
|
adminRateLimiter;
|
|
323
|
+
/** Rate limiter for session creation (prevents session slot exhaustion) */
|
|
324
|
+
sessionRateLimiter;
|
|
309
325
|
/** Server start time (ms since epoch) */
|
|
310
326
|
startedAt = Date.now();
|
|
311
327
|
/** Whether the server is draining (shutting down gracefully) */
|
|
@@ -358,6 +374,8 @@ class PayGateServer {
|
|
|
358
374
|
this.adminKeys.bootstrap(this.bootstrapAdminKey);
|
|
359
375
|
// Admin endpoint rate limiter: configurable requests/min per source IP (brute-force protection)
|
|
360
376
|
this.adminRateLimiter = new rate_limiter_1.RateLimiter(this.config.adminRateLimit ?? 120);
|
|
377
|
+
// Session creation rate limiter: max 60 new sessions/min per IP (prevents session slot exhaustion)
|
|
378
|
+
this.sessionRateLimiter = new rate_limiter_1.RateLimiter(this.config.sessionRateLimit ?? 60);
|
|
361
379
|
this.gate = new gate_1.Gate(this.config, statePath);
|
|
362
380
|
this.gate.store.logger = this.logger;
|
|
363
381
|
// Multi-server mode: use Router
|
|
@@ -592,7 +610,7 @@ class PayGateServer {
|
|
|
592
610
|
this.sendError(res, 408, 'Request timeout');
|
|
593
611
|
}
|
|
594
612
|
else {
|
|
595
|
-
this.logger.error('Unhandled request error', { error: msg, url: req.url, method: req.method });
|
|
613
|
+
this.logger.error('Unhandled request error', { error: msg, url: sanitizeLogUrl(req.url), method: req.method });
|
|
596
614
|
this.sendError(res, 500, 'Internal server error');
|
|
597
615
|
}
|
|
598
616
|
}
|
|
@@ -601,6 +619,7 @@ class PayGateServer {
|
|
|
601
619
|
this.server.requestTimeout = this.config.requestTimeoutMs ?? 30_000; // 30s max per request (Node default: 0 = none)
|
|
602
620
|
this.server.headersTimeout = this.config.headersTimeoutMs ?? 10_000; // 10s to receive headers (Node default: 60s)
|
|
603
621
|
this.server.keepAliveTimeout = this.config.keepAliveTimeoutMs ?? 65_000; // 65s keep-alive (> typical 60s LB idle)
|
|
622
|
+
this.server.maxConnections = this.config.maxConnections ?? 10_000; // Cap concurrent TCP connections to prevent FD exhaustion
|
|
604
623
|
if (this.config.maxRequestsPerSocket) {
|
|
605
624
|
this.server.maxRequestsPerSocket = this.config.maxRequestsPerSocket; // Limit pipelined requests per socket
|
|
606
625
|
}
|
|
@@ -750,7 +769,8 @@ class PayGateServer {
|
|
|
750
769
|
return this.handleCreateKey(req, res);
|
|
751
770
|
if (req.method === 'GET')
|
|
752
771
|
return this.handleListKeys(req, res);
|
|
753
|
-
|
|
772
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
773
|
+
return;
|
|
754
774
|
case '/keys/revoke':
|
|
755
775
|
return this.handleRevokeKey(req, res);
|
|
756
776
|
case '/keys/suspend':
|
|
@@ -845,7 +865,8 @@ class PayGateServer {
|
|
|
845
865
|
return this.handleListTemplates(req, res);
|
|
846
866
|
if (req.method === 'POST')
|
|
847
867
|
return this.handleCreateTemplate(req, res);
|
|
848
|
-
|
|
868
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
869
|
+
return;
|
|
849
870
|
case '/keys/templates/delete':
|
|
850
871
|
return this.handleDeleteTemplate(req, res);
|
|
851
872
|
case '/topup':
|
|
@@ -918,14 +939,16 @@ class PayGateServer {
|
|
|
918
939
|
return this.handleGetAlerts(req, res);
|
|
919
940
|
if (req.method === 'POST')
|
|
920
941
|
return this.handleConfigureAlerts(req, res);
|
|
921
|
-
|
|
942
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
943
|
+
return;
|
|
922
944
|
// ─── Webhook admin endpoints ─────────────────────────────────────
|
|
923
945
|
case '/webhooks/dead-letter':
|
|
924
946
|
if (req.method === 'GET')
|
|
925
947
|
return this.handleGetDeadLetters(req, res);
|
|
926
948
|
if (req.method === 'DELETE')
|
|
927
949
|
return this.handleClearDeadLetters(req, res);
|
|
928
|
-
|
|
950
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or DELETE.');
|
|
951
|
+
return;
|
|
929
952
|
case '/webhooks/replay':
|
|
930
953
|
return this.handleWebhookReplay(req, res);
|
|
931
954
|
case '/webhooks/stats':
|
|
@@ -943,7 +966,8 @@ class PayGateServer {
|
|
|
943
966
|
return this.handleListWebhookFilters(req, res);
|
|
944
967
|
if (req.method === 'POST')
|
|
945
968
|
return this.handleCreateWebhookFilter(req, res);
|
|
946
|
-
|
|
969
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
970
|
+
return;
|
|
947
971
|
case '/webhooks/filters/update':
|
|
948
972
|
return this.handleUpdateWebhookFilter(req, res);
|
|
949
973
|
case '/webhooks/filters/delete':
|
|
@@ -954,7 +978,8 @@ class PayGateServer {
|
|
|
954
978
|
return this.handleListTeams(req, res);
|
|
955
979
|
if (req.method === 'POST')
|
|
956
980
|
return this.handleCreateTeam(req, res);
|
|
957
|
-
|
|
981
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
982
|
+
return;
|
|
958
983
|
case '/teams/update':
|
|
959
984
|
return this.handleUpdateTeam(req, res);
|
|
960
985
|
case '/teams/delete':
|
|
@@ -972,7 +997,8 @@ class PayGateServer {
|
|
|
972
997
|
case '/tokens':
|
|
973
998
|
if (req.method === 'POST')
|
|
974
999
|
return this.handleCreateToken(req, res);
|
|
975
|
-
|
|
1000
|
+
this.sendError(res, 405, 'Method not allowed. Use POST.');
|
|
1001
|
+
return;
|
|
976
1002
|
case '/tokens/revoke':
|
|
977
1003
|
return this.handleRevokeToken(req, res);
|
|
978
1004
|
case '/tokens/revoked':
|
|
@@ -983,7 +1009,8 @@ class PayGateServer {
|
|
|
983
1009
|
return this.handleCreateAdminKey(req, res);
|
|
984
1010
|
if (req.method === 'GET')
|
|
985
1011
|
return this.handleListAdminKeys(req, res);
|
|
986
|
-
|
|
1012
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
1013
|
+
return;
|
|
987
1014
|
case '/admin/keys/revoke':
|
|
988
1015
|
return this.handleRevokeAdminKey(req, res);
|
|
989
1016
|
case '/admin/keys/rotate-bootstrap':
|
|
@@ -1318,7 +1345,8 @@ class PayGateServer {
|
|
|
1318
1345
|
return this.handleListGroups(req, res);
|
|
1319
1346
|
if (req.method === 'POST')
|
|
1320
1347
|
return this.handleCreateGroup(req, res);
|
|
1321
|
-
|
|
1348
|
+
this.sendError(res, 405, 'Method not allowed. Use GET or POST.');
|
|
1349
|
+
return;
|
|
1322
1350
|
case '/groups/update':
|
|
1323
1351
|
return this.handleUpdateGroup(req, res);
|
|
1324
1352
|
case '/groups/delete':
|
|
@@ -1382,6 +1410,8 @@ class PayGateServer {
|
|
|
1382
1410
|
this.sendError(res, 405, 'Method not allowed');
|
|
1383
1411
|
return;
|
|
1384
1412
|
}
|
|
1413
|
+
if (!this.requireJsonContentType(req, res))
|
|
1414
|
+
return;
|
|
1385
1415
|
const body = await this.readBody(req);
|
|
1386
1416
|
let request;
|
|
1387
1417
|
try {
|
|
@@ -1418,6 +1448,15 @@ class PayGateServer {
|
|
|
1418
1448
|
// Session management: reuse or create
|
|
1419
1449
|
let sessionId = req.headers['mcp-session-id'];
|
|
1420
1450
|
if (!sessionId || !this.sessions.getSession(sessionId)) {
|
|
1451
|
+
// Rate limit session creation per IP to prevent session slot exhaustion
|
|
1452
|
+
const sessionIp = resolveClientIp(req, this.config.trustedProxies);
|
|
1453
|
+
const sessionRateResult = this.sessionRateLimiter.check(`sess:${sessionIp}`);
|
|
1454
|
+
if (!sessionRateResult.allowed) {
|
|
1455
|
+
res.setHeader('Retry-After', String(Math.ceil(sessionRateResult.resetInMs / 1000)));
|
|
1456
|
+
this.sendError(res, 429, 'Too many sessions created. Try again later.');
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
this.sessionRateLimiter.record(`sess:${sessionIp}`);
|
|
1421
1460
|
sessionId = this.sessions.createSession(apiKey);
|
|
1422
1461
|
this.audit.log('session.created', (0, audit_1.maskKeyForAudit)(apiKey || 'anonymous'), `Session created`, {
|
|
1423
1462
|
requestId,
|
|
@@ -1640,6 +1679,15 @@ class PayGateServer {
|
|
|
1640
1679
|
// Session: reuse or create
|
|
1641
1680
|
let sessionId = req.headers['mcp-session-id'];
|
|
1642
1681
|
if (!sessionId || !this.sessions.getSession(sessionId)) {
|
|
1682
|
+
// Rate limit session creation per IP to prevent session slot exhaustion
|
|
1683
|
+
const sessionIp = resolveClientIp(req, this.config.trustedProxies);
|
|
1684
|
+
const sessionRateResult = this.sessionRateLimiter.check(`sess:${sessionIp}`);
|
|
1685
|
+
if (!sessionRateResult.allowed) {
|
|
1686
|
+
res.setHeader('Retry-After', String(Math.ceil(sessionRateResult.resetInMs / 1000)));
|
|
1687
|
+
this.sendError(res, 429, 'Too many sessions created. Try again later.');
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
this.sessionRateLimiter.record(`sess:${sessionIp}`);
|
|
1643
1691
|
sessionId = this.sessions.createSession(apiKey);
|
|
1644
1692
|
}
|
|
1645
1693
|
// Register this SSE connection
|
|
@@ -1950,6 +1998,15 @@ class PayGateServer {
|
|
|
1950
1998
|
const params = new URLSearchParams(urlParts[1] || '');
|
|
1951
1999
|
const namespace = params.get('namespace') || undefined;
|
|
1952
2000
|
const status = this.gate.getStatus(namespace);
|
|
2001
|
+
// Cap the keys array to prevent unbounded response sizes with many API keys.
|
|
2002
|
+
// The full paginated listing is available via GET /admin/keys.
|
|
2003
|
+
const MAX_STATUS_KEYS = 1000;
|
|
2004
|
+
if (status.keys && status.keys.length > MAX_STATUS_KEYS) {
|
|
2005
|
+
const totalKeys = status.keys.length;
|
|
2006
|
+
status.keys = status.keys.slice(0, MAX_STATUS_KEYS);
|
|
2007
|
+
status.keysTruncated = true;
|
|
2008
|
+
status.totalKeyCount = totalKeys;
|
|
2009
|
+
}
|
|
1953
2010
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1954
2011
|
res.end(JSON.stringify(status, null, 2));
|
|
1955
2012
|
}
|
|
@@ -4224,6 +4281,8 @@ class PayGateServer {
|
|
|
4224
4281
|
this.sendError(res, 405, 'Method not allowed');
|
|
4225
4282
|
return;
|
|
4226
4283
|
}
|
|
4284
|
+
if (!this.requireJsonContentType(req, res))
|
|
4285
|
+
return;
|
|
4227
4286
|
const body = await this.readBody(req);
|
|
4228
4287
|
let params;
|
|
4229
4288
|
try {
|
|
@@ -4360,13 +4419,15 @@ class PayGateServer {
|
|
|
4360
4419
|
this.sendError(res, 405, 'Method not allowed');
|
|
4361
4420
|
return;
|
|
4362
4421
|
}
|
|
4422
|
+
if (!this.requireJsonContentType(req, res))
|
|
4423
|
+
return;
|
|
4363
4424
|
const body = await this.readBody(req);
|
|
4364
4425
|
let params;
|
|
4365
4426
|
try {
|
|
4366
4427
|
params = safeJsonParse(body);
|
|
4367
4428
|
}
|
|
4368
4429
|
catch {
|
|
4369
|
-
// Try URL-encoded form data
|
|
4430
|
+
// Try URL-encoded form data (RFC 6749)
|
|
4370
4431
|
params = {};
|
|
4371
4432
|
const query = new URLSearchParams(body);
|
|
4372
4433
|
for (const [k, v] of query)
|
|
@@ -4499,7 +4560,8 @@ class PayGateServer {
|
|
|
4499
4560
|
const from = params.get('from') || undefined;
|
|
4500
4561
|
const to = params.get('to') || undefined;
|
|
4501
4562
|
const granularity = (params.get('granularity') || 'hourly');
|
|
4502
|
-
const topN = params.get('top') ? parseInt(params.get('top'), 10) : 10
|
|
4563
|
+
const topN = clampInt(params.get('top') ? parseInt(params.get('top'), 10) : undefined, 1, 1000, 10 // default 10, max 1000 — prevents memory exhaustion from ?top=999999999
|
|
4564
|
+
);
|
|
4503
4565
|
const namespace = params.get('namespace') || undefined;
|
|
4504
4566
|
const events = this.gate.meter.getEvents(undefined, namespace);
|
|
4505
4567
|
const report = this.analytics.report(events, { from, to, granularity, topN });
|
|
@@ -10220,8 +10282,8 @@ class PayGateServer {
|
|
|
10220
10282
|
this.adminRateLimiter.record(`ip:${sourceIp}`);
|
|
10221
10283
|
const record = adminKey ? this.adminKeys.validate(adminKey) : null;
|
|
10222
10284
|
if (!record) {
|
|
10223
|
-
this.audit.log('admin.auth_failed', 'unknown', `Admin auth failed on ${req.url}`, {
|
|
10224
|
-
url: req.url,
|
|
10285
|
+
this.audit.log('admin.auth_failed', 'unknown', `Admin auth failed on ${sanitizeLogUrl(req.url)}`, {
|
|
10286
|
+
url: sanitizeLogUrl(req.url),
|
|
10225
10287
|
method: req.method,
|
|
10226
10288
|
});
|
|
10227
10289
|
this.sendError(res, 401, 'Invalid admin key');
|
|
@@ -10229,8 +10291,8 @@ class PayGateServer {
|
|
|
10229
10291
|
}
|
|
10230
10292
|
// Role-based permission check (if a minimum role is specified)
|
|
10231
10293
|
if (minRole && admin_keys_1.ROLE_HIERARCHY[record.role] < admin_keys_1.ROLE_HIERARCHY[minRole]) {
|
|
10232
|
-
this.audit.log('admin.auth_failed', adminKey.slice(0, 7) + '...' + adminKey.slice(-4), `Insufficient role for ${req.url} (need ${minRole}, have ${record.role})`, {
|
|
10233
|
-
url: req.url,
|
|
10294
|
+
this.audit.log('admin.auth_failed', adminKey.slice(0, 7) + '...' + adminKey.slice(-4), `Insufficient role for ${sanitizeLogUrl(req.url)} (need ${minRole}, have ${record.role})`, {
|
|
10295
|
+
url: sanitizeLogUrl(req.url),
|
|
10234
10296
|
method: req.method,
|
|
10235
10297
|
requiredRole: minRole,
|
|
10236
10298
|
currentRole: record.role,
|
|
@@ -10238,6 +10300,9 @@ class PayGateServer {
|
|
|
10238
10300
|
this.sendError(res, 403, 'Insufficient permissions', { requiredRole: minRole, currentRole: record.role });
|
|
10239
10301
|
return false;
|
|
10240
10302
|
}
|
|
10303
|
+
// Content-Type enforcement for POST requests (after auth, before body read)
|
|
10304
|
+
if (req.method === 'POST' && !this.requireJsonContentType(req, res))
|
|
10305
|
+
return false;
|
|
10241
10306
|
return true;
|
|
10242
10307
|
}
|
|
10243
10308
|
// ─── /teams — Team management ────────────────────────────────────────────
|
|
@@ -11060,6 +11125,28 @@ class PayGateServer {
|
|
|
11060
11125
|
}
|
|
11061
11126
|
return '*';
|
|
11062
11127
|
}
|
|
11128
|
+
/**
|
|
11129
|
+
* Check Content-Type is JSON. Returns true if valid, false and sends 415 if not.
|
|
11130
|
+
* Exempt paths (like /oauth/token) accept form-urlencoded per RFC 6749.
|
|
11131
|
+
*/
|
|
11132
|
+
requireJsonContentType(req, res) {
|
|
11133
|
+
if (req.method !== 'POST')
|
|
11134
|
+
return true; // Only enforce for POST
|
|
11135
|
+
const ct = (req.headers['content-type'] || '').toLowerCase();
|
|
11136
|
+
const url = req.url?.split('?')[0] || '/';
|
|
11137
|
+
if (url === '/oauth/token') {
|
|
11138
|
+
// OAuth token endpoint accepts both JSON and form-encoded per RFC 6749
|
|
11139
|
+
if (ct.startsWith('application/json') || ct.startsWith('application/x-www-form-urlencoded'))
|
|
11140
|
+
return true;
|
|
11141
|
+
this.sendError(res, 415, 'Unsupported Media Type. Use application/json or application/x-www-form-urlencoded');
|
|
11142
|
+
return false;
|
|
11143
|
+
}
|
|
11144
|
+
if (!ct.startsWith('application/json')) {
|
|
11145
|
+
this.sendError(res, 415, 'Unsupported Media Type. Use application/json');
|
|
11146
|
+
return false;
|
|
11147
|
+
}
|
|
11148
|
+
return true;
|
|
11149
|
+
}
|
|
11063
11150
|
readBody(req) {
|
|
11064
11151
|
return new Promise((resolve, reject) => {
|
|
11065
11152
|
let body = '';
|
|
@@ -11217,6 +11304,7 @@ class PayGateServer {
|
|
|
11217
11304
|
this.tokens.destroy();
|
|
11218
11305
|
this.expiryScanner.destroy();
|
|
11219
11306
|
this.adminRateLimiter.destroy();
|
|
11307
|
+
this.sessionRateLimiter.destroy();
|
|
11220
11308
|
if (this.redisSync) {
|
|
11221
11309
|
await this.redisSync.destroy();
|
|
11222
11310
|
}
|