@fitzzero/quickdraw-core 3.5.0 → 3.7.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 +113 -64
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +61 -66
- package/dist/client/index.js.map +1 -1
- package/dist/client/testing.d.ts +1 -1
- package/dist/client/testing.js.map +1 -1
- package/dist/server/express/index.d.ts +37 -0
- package/dist/server/express/index.js +60 -0
- package/dist/server/express/index.js.map +1 -0
- package/dist/server/index.d.ts +227 -1
- package/dist/server/index.js +400 -142
- package/dist/server/index.js.map +1 -1
- package/dist/server/testing-prisma.d.ts +101 -0
- package/dist/server/testing-prisma.js +172 -0
- package/dist/server/testing-prisma.js.map +1 -0
- package/dist/server/testing.js +5 -22
- package/dist/server/testing.js.map +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/{types-BJj94_XW.d.ts → types-B1z5aXdR.d.ts} +11 -0
- package/eslint-config/index.mjs +1 -2
- package/eslint-plugin-quickdraw/index.mjs +15 -7
- package/eslint-plugin-quickdraw/rules/no-cross-service-mutations.mjs +31 -32
- package/eslint-plugin-quickdraw/rules/no-direct-prisma-mutations.mjs +17 -18
- package/eslint-plugin-quickdraw/rules/no-raw-button-strings.mjs +52 -0
- package/eslint-plugin-quickdraw/rules/no-raw-service-room-string.mjs +140 -0
- package/eslint-plugin-quickdraw/rules/no-raw-socket-emit.mjs +20 -22
- package/eslint-plugin-quickdraw/rules/no-raw-socket-on.mjs +17 -17
- package/eslint-plugin-quickdraw/rules/no-raw-tooltip-strings.mjs +59 -0
- package/eslint-plugin-quickdraw/rules/no-raw-typography-strings.mjs +52 -0
- package/eslint-plugin-quickdraw/rules/no-service-method-record.mjs +3 -3
- package/eslint-plugin-quickdraw/rules/no-unsafe-payload-cast.mjs +8 -8
- package/eslint-plugin-quickdraw/rules/require-zod-schema.mjs +15 -20
- package/package.json +74 -50
package/dist/server/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import { createServer } from 'http';
|
|
3
3
|
import { Server } from 'socket.io';
|
|
4
4
|
import * as jose from 'jose';
|
|
5
|
+
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
|
|
5
6
|
import * as readline from 'readline';
|
|
6
7
|
import * as fs from 'fs';
|
|
7
8
|
import * as path from 'path';
|
|
@@ -16,18 +17,8 @@ var consoleLogger = {
|
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
// src/server/utils/zodToAdminFields.ts
|
|
19
|
-
var NON_EDITABLE_FIELDS = /* @__PURE__ */ new Set([
|
|
20
|
-
|
|
21
|
-
"createdAt",
|
|
22
|
-
"updatedAt",
|
|
23
|
-
"created_at",
|
|
24
|
-
"updated_at"
|
|
25
|
-
]);
|
|
26
|
-
var DEFAULT_HIDDEN_FIELDS = /* @__PURE__ */ new Set([
|
|
27
|
-
"acl",
|
|
28
|
-
"serviceAccess",
|
|
29
|
-
"service_access"
|
|
30
|
-
]);
|
|
20
|
+
var NON_EDITABLE_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "created_at", "updated_at"]);
|
|
21
|
+
var DEFAULT_HIDDEN_FIELDS = /* @__PURE__ */ new Set(["acl", "serviceAccess", "service_access"]);
|
|
31
22
|
function toLabel(fieldName) {
|
|
32
23
|
return fieldName.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
33
24
|
}
|
|
@@ -68,9 +59,7 @@ function getEnumValues(zodType) {
|
|
|
68
59
|
}
|
|
69
60
|
if (typeName === "ZodNativeEnum") {
|
|
70
61
|
const enumObj = zodType._def.values;
|
|
71
|
-
return Object.values(enumObj).filter(
|
|
72
|
-
(v) => typeof v === "string"
|
|
73
|
-
);
|
|
62
|
+
return Object.values(enumObj).filter((v) => typeof v === "string");
|
|
74
63
|
}
|
|
75
64
|
return void 0;
|
|
76
65
|
}
|
|
@@ -167,13 +156,9 @@ function getDefaultEntityFields() {
|
|
|
167
156
|
}
|
|
168
157
|
function mergeWithDefaultFields(schemaFields, options = {}) {
|
|
169
158
|
const { includeDefaults = ["id", "createdAt"] } = options;
|
|
170
|
-
const defaultFields = getDefaultEntityFields().filter(
|
|
171
|
-
(f) => includeDefaults.includes(f.name)
|
|
172
|
-
);
|
|
159
|
+
const defaultFields = getDefaultEntityFields().filter((f) => includeDefaults.includes(f.name));
|
|
173
160
|
const defaultNames = new Set(defaultFields.map((f) => f.name));
|
|
174
|
-
const filteredSchemaFields = schemaFields.filter(
|
|
175
|
-
(f) => !defaultNames.has(f.name)
|
|
176
|
-
);
|
|
161
|
+
const filteredSchemaFields = schemaFields.filter((f) => !defaultNames.has(f.name));
|
|
177
162
|
return [...defaultFields, ...filteredSchemaFields];
|
|
178
163
|
}
|
|
179
164
|
|
|
@@ -258,9 +243,7 @@ var BaseService = class {
|
|
|
258
243
|
this.subscribers.get(entryId).add(socket);
|
|
259
244
|
const roomName = this.getRoomName(entryId);
|
|
260
245
|
void socket.join(roomName);
|
|
261
|
-
this.logger.debug(
|
|
262
|
-
`User ${socket.userId} subscribed to ${entryId} (room: ${roomName})`
|
|
263
|
-
);
|
|
246
|
+
this.logger.debug(`User ${socket.userId} subscribed to ${entryId} (room: ${roomName})`);
|
|
264
247
|
const entity = await this.findById(entryId);
|
|
265
248
|
if (!entity) return null;
|
|
266
249
|
return this.filterEntityForSubscriber(entity, socket, entryId);
|
|
@@ -303,11 +286,7 @@ var BaseService = class {
|
|
|
303
286
|
results[entryId] = null;
|
|
304
287
|
continue;
|
|
305
288
|
}
|
|
306
|
-
results[entryId] = this.filterEntityForSubscriber(
|
|
307
|
-
entity,
|
|
308
|
-
socket,
|
|
309
|
-
entryId
|
|
310
|
-
);
|
|
289
|
+
results[entryId] = this.filterEntityForSubscriber(entity, socket, entryId);
|
|
311
290
|
}
|
|
312
291
|
this.logger.debug(
|
|
313
292
|
`User ${socket.userId} batch-subscribed to ${allowedIds.length}/${entryIds.length} entities`
|
|
@@ -325,12 +304,7 @@ var BaseService = class {
|
|
|
325
304
|
const results = /* @__PURE__ */ new Map();
|
|
326
305
|
await Promise.all(
|
|
327
306
|
entryIds.map(async (id) => {
|
|
328
|
-
const allowed = await this.checkSubscriptionAccess(
|
|
329
|
-
userId,
|
|
330
|
-
id,
|
|
331
|
-
requiredLevel,
|
|
332
|
-
socket
|
|
333
|
-
);
|
|
307
|
+
const allowed = await this.checkSubscriptionAccess(userId, id, requiredLevel, socket);
|
|
334
308
|
results.set(id, allowed);
|
|
335
309
|
})
|
|
336
310
|
);
|
|
@@ -344,9 +318,7 @@ var BaseService = class {
|
|
|
344
318
|
* Default: fetches each entity individually in parallel.
|
|
345
319
|
*/
|
|
346
320
|
async findByIds(ids) {
|
|
347
|
-
const results = await Promise.all(
|
|
348
|
-
ids.map((id) => this.findById(id))
|
|
349
|
-
);
|
|
321
|
+
const results = await Promise.all(ids.map((id) => this.findById(id)));
|
|
350
322
|
return results.filter((e) => e !== null);
|
|
351
323
|
}
|
|
352
324
|
/**
|
|
@@ -410,9 +382,7 @@ var BaseService = class {
|
|
|
410
382
|
return;
|
|
411
383
|
}
|
|
412
384
|
anySocket.to(roomName).emit(eventName, data);
|
|
413
|
-
this.logger.debug(
|
|
414
|
-
`Emitted ${eventName} to room ${roomName} (via socket fallback)`
|
|
415
|
-
);
|
|
385
|
+
this.logger.debug(`Emitted ${eventName} to room ${roomName} (via socket fallback)`);
|
|
416
386
|
}
|
|
417
387
|
/**
|
|
418
388
|
* Emit a custom event to a specific user's room.
|
|
@@ -689,10 +659,7 @@ var BaseService = class {
|
|
|
689
659
|
schema: options?.schema,
|
|
690
660
|
resolveEntryId: options?.resolveEntryId
|
|
691
661
|
};
|
|
692
|
-
this.publicMethods.set(
|
|
693
|
-
name,
|
|
694
|
-
definition
|
|
695
|
-
);
|
|
662
|
+
this.publicMethods.set(name, definition);
|
|
696
663
|
return definition;
|
|
697
664
|
}
|
|
698
665
|
/**
|
|
@@ -725,9 +692,7 @@ var BaseService = class {
|
|
|
725
692
|
}
|
|
726
693
|
}
|
|
727
694
|
if (missing.length > 0) {
|
|
728
|
-
throw new Error(
|
|
729
|
-
`${this.serviceName}: Missing method implementations: ${missing.join(", ")}`
|
|
730
|
-
);
|
|
695
|
+
throw new Error(`${this.serviceName}: Missing method implementations: ${missing.join(", ")}`);
|
|
731
696
|
}
|
|
732
697
|
}
|
|
733
698
|
// ===========================================================================
|
|
@@ -758,15 +723,7 @@ var BaseService = class {
|
|
|
758
723
|
* ```
|
|
759
724
|
*/
|
|
760
725
|
installAdminMethods(options) {
|
|
761
|
-
const {
|
|
762
|
-
expose,
|
|
763
|
-
access,
|
|
764
|
-
schema,
|
|
765
|
-
displayName,
|
|
766
|
-
tableColumns,
|
|
767
|
-
hiddenFields,
|
|
768
|
-
fieldOverrides
|
|
769
|
-
} = options;
|
|
726
|
+
const { expose, access, schema, displayName, tableColumns, hiddenFields, fieldOverrides } = options;
|
|
770
727
|
if (schema) {
|
|
771
728
|
const schemaFields = zodToAdminFields(schema, {
|
|
772
729
|
hiddenFields,
|
|
@@ -820,9 +777,7 @@ var BaseService = class {
|
|
|
820
777
|
name: "adminCreate",
|
|
821
778
|
access: access.create,
|
|
822
779
|
handler: async (payload, _context) => {
|
|
823
|
-
return await this.adminCreate(
|
|
824
|
-
payload.data
|
|
825
|
-
);
|
|
780
|
+
return await this.adminCreate(payload.data);
|
|
826
781
|
}
|
|
827
782
|
});
|
|
828
783
|
}
|
|
@@ -862,9 +817,7 @@ var BaseService = class {
|
|
|
862
817
|
name: "adminGetSubscribers",
|
|
863
818
|
access: access.getSubscribers,
|
|
864
819
|
handler: async (payload, _context) => {
|
|
865
|
-
return this.adminGetSubscribers(
|
|
866
|
-
payload.entryId
|
|
867
|
-
);
|
|
820
|
+
return this.adminGetSubscribers(payload.entryId);
|
|
868
821
|
},
|
|
869
822
|
resolveEntryId: (payload) => payload.entryId
|
|
870
823
|
});
|
|
@@ -874,9 +827,7 @@ var BaseService = class {
|
|
|
874
827
|
name: "adminReemit",
|
|
875
828
|
access: access.reemit,
|
|
876
829
|
handler: async (payload, _context) => {
|
|
877
|
-
return await this.adminReemit(
|
|
878
|
-
payload.entryId
|
|
879
|
-
);
|
|
830
|
+
return await this.adminReemit(payload.entryId);
|
|
880
831
|
},
|
|
881
832
|
resolveEntryId: (payload) => payload.entryId
|
|
882
833
|
});
|
|
@@ -886,16 +837,12 @@ var BaseService = class {
|
|
|
886
837
|
name: "adminUnsubscribeAll",
|
|
887
838
|
access: access.unsubscribeAll,
|
|
888
839
|
handler: async (payload, _context) => {
|
|
889
|
-
return this.adminUnsubscribeAll(
|
|
890
|
-
payload.entryId
|
|
891
|
-
);
|
|
840
|
+
return this.adminUnsubscribeAll(payload.entryId);
|
|
892
841
|
},
|
|
893
842
|
resolveEntryId: (payload) => payload.entryId
|
|
894
843
|
});
|
|
895
844
|
}
|
|
896
|
-
const installedMethods = Object.keys(expose).filter(
|
|
897
|
-
(k) => expose[k]
|
|
898
|
-
);
|
|
845
|
+
const installedMethods = Object.keys(expose).filter((k) => expose[k]);
|
|
899
846
|
if (shouldExposeMeta) installedMethods.push("meta");
|
|
900
847
|
this.logger.info(`Installed admin methods: ${installedMethods.join(", ")}`);
|
|
901
848
|
}
|
|
@@ -1091,21 +1038,9 @@ var ServiceRegistry = class {
|
|
|
1091
1038
|
this.registerMethodListener(socket, eventName, method, service);
|
|
1092
1039
|
}
|
|
1093
1040
|
for (const [serviceName, service] of this.services) {
|
|
1094
|
-
this.registerSubscriptionListener(
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
service
|
|
1098
|
-
);
|
|
1099
|
-
this.registerBatchSubscriptionListener(
|
|
1100
|
-
socket,
|
|
1101
|
-
`${serviceName}:batchSubscribe`,
|
|
1102
|
-
service
|
|
1103
|
-
);
|
|
1104
|
-
this.registerUnsubscriptionListener(
|
|
1105
|
-
socket,
|
|
1106
|
-
`${serviceName}:unsubscribe`,
|
|
1107
|
-
service
|
|
1108
|
-
);
|
|
1041
|
+
this.registerSubscriptionListener(socket, `${serviceName}:subscribe`, service);
|
|
1042
|
+
this.registerBatchSubscriptionListener(socket, `${serviceName}:batchSubscribe`, service);
|
|
1043
|
+
this.registerUnsubscriptionListener(socket, `${serviceName}:unsubscribe`, service);
|
|
1109
1044
|
}
|
|
1110
1045
|
}
|
|
1111
1046
|
/**
|
|
@@ -1238,10 +1173,7 @@ var ServiceRegistry = class {
|
|
|
1238
1173
|
if (this.logPayloads) {
|
|
1239
1174
|
logContext.payload = payload;
|
|
1240
1175
|
}
|
|
1241
|
-
this.logger.error(
|
|
1242
|
-
`User ${userIdShort} ${serviceName}.${methodName} -> fail`,
|
|
1243
|
-
logContext
|
|
1244
|
-
);
|
|
1176
|
+
this.logger.error(`User ${userIdShort} ${serviceName}.${methodName} -> fail`, logContext);
|
|
1245
1177
|
const errorResponse = {
|
|
1246
1178
|
success: false,
|
|
1247
1179
|
error: error instanceof Error ? error.message : "Internal error",
|
|
@@ -1437,9 +1369,7 @@ function createQuickdrawServer(options) {
|
|
|
1437
1369
|
});
|
|
1438
1370
|
httpServer.listen(options.port, "0.0.0.0", () => {
|
|
1439
1371
|
serverLogger.info(`Server listening on port ${options.port}`);
|
|
1440
|
-
serverLogger.info(
|
|
1441
|
-
`Registered services: ${registry.getServices().join(", ")}`
|
|
1442
|
-
);
|
|
1372
|
+
serverLogger.info(`Registered services: ${registry.getServices().join(", ")}`);
|
|
1443
1373
|
});
|
|
1444
1374
|
const shutdown = async (signal) => {
|
|
1445
1375
|
serverLogger.info(`Received ${signal}, starting graceful shutdown...`);
|
|
@@ -1475,13 +1405,7 @@ function createQuickdrawServer(options) {
|
|
|
1475
1405
|
// src/server/redis.ts
|
|
1476
1406
|
async function setupRedisAdapter(io, options = {}) {
|
|
1477
1407
|
const logger = options.logger?.child({ service: "RedisAdapter" }) ?? consoleLogger.child({ service: "RedisAdapter" });
|
|
1478
|
-
const {
|
|
1479
|
-
host = "localhost",
|
|
1480
|
-
port = 6379,
|
|
1481
|
-
password,
|
|
1482
|
-
db = 0,
|
|
1483
|
-
keyPrefix = "socket.io"
|
|
1484
|
-
} = options;
|
|
1408
|
+
const { host = "localhost", port = 6379, password, db = 0, keyPrefix = "socket.io" } = options;
|
|
1485
1409
|
try {
|
|
1486
1410
|
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1487
1411
|
const [redisAdapterModule, redisModule] = await Promise.all([
|
|
@@ -1497,7 +1421,9 @@ async function setupRedisAdapter(io, options = {}) {
|
|
|
1497
1421
|
});
|
|
1498
1422
|
const subClient = pubClient.duplicate();
|
|
1499
1423
|
await Promise.all([pubClient.connect(), subClient.connect()]);
|
|
1500
|
-
io.adapter(
|
|
1424
|
+
io.adapter(
|
|
1425
|
+
createAdapter(pubClient, subClient, { key: keyPrefix })
|
|
1426
|
+
);
|
|
1501
1427
|
logger.info(`Redis adapter connected to ${host}:${port}`);
|
|
1502
1428
|
const cleanup = async () => {
|
|
1503
1429
|
try {
|
|
@@ -1529,10 +1455,7 @@ async function setupRedisAdapter(io, options = {}) {
|
|
|
1529
1455
|
async function isRedisAdapterAvailable() {
|
|
1530
1456
|
try {
|
|
1531
1457
|
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1532
|
-
await Promise.all([
|
|
1533
|
-
dynamicImport("@socket.io/redis-adapter"),
|
|
1534
|
-
dynamicImport("redis")
|
|
1535
|
-
]);
|
|
1458
|
+
await Promise.all([dynamicImport("@socket.io/redis-adapter"), dynamicImport("redis")]);
|
|
1536
1459
|
return true;
|
|
1537
1460
|
} catch {
|
|
1538
1461
|
return false;
|
|
@@ -1541,11 +1464,7 @@ async function isRedisAdapterAvailable() {
|
|
|
1541
1464
|
|
|
1542
1465
|
// src/server/rateLimit.ts
|
|
1543
1466
|
function createRateLimiter(options = {}) {
|
|
1544
|
-
const {
|
|
1545
|
-
windowMs = 6e4,
|
|
1546
|
-
maxRequests = 100,
|
|
1547
|
-
excludeEvents = []
|
|
1548
|
-
} = options;
|
|
1467
|
+
const { windowMs = 6e4, maxRequests = 100, excludeEvents = [] } = options;
|
|
1549
1468
|
const entries = /* @__PURE__ */ new Map();
|
|
1550
1469
|
const cleanupInterval = setInterval(() => {
|
|
1551
1470
|
const now = Date.now();
|
|
@@ -1769,6 +1688,13 @@ var discordProvider = {
|
|
|
1769
1688
|
};
|
|
1770
1689
|
}
|
|
1771
1690
|
};
|
|
1691
|
+
function getDiscordAvatarUrl(user) {
|
|
1692
|
+
if (user.avatar) {
|
|
1693
|
+
return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
|
|
1694
|
+
}
|
|
1695
|
+
const index = user.discriminator !== "0" ? parseInt(user.discriminator, 10) % 5 : (BigInt(user.id) >> BigInt(22)) % BigInt(6);
|
|
1696
|
+
return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
|
|
1697
|
+
}
|
|
1772
1698
|
|
|
1773
1699
|
// src/server/auth/google.ts
|
|
1774
1700
|
var googleProvider = {
|
|
@@ -1789,6 +1715,306 @@ var googleProvider = {
|
|
|
1789
1715
|
}
|
|
1790
1716
|
};
|
|
1791
1717
|
|
|
1718
|
+
// src/server/auth/validateOrigin.ts
|
|
1719
|
+
var OAUTH_RETURN_ORIGIN_COOKIE = "oauth_return_origin";
|
|
1720
|
+
var CODESPACE_REGEX = /^https:\/\/[a-z0-9-]+-[a-z0-9-]+-\d+\.app\.github\.dev$/;
|
|
1721
|
+
var LOCALHOST_REGEX = /^http:\/\/localhost:\d+$/;
|
|
1722
|
+
function envExtraOrigins() {
|
|
1723
|
+
const raw = process.env.EXTRA_ALLOWED_ORIGINS;
|
|
1724
|
+
if (!raw) return [];
|
|
1725
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1726
|
+
}
|
|
1727
|
+
function validateRedirectOrigin(origin, options = {}) {
|
|
1728
|
+
if (!origin) return null;
|
|
1729
|
+
const clientUrl = options.clientUrl ?? process.env.CLIENT_URL;
|
|
1730
|
+
let parsedOrigin;
|
|
1731
|
+
try {
|
|
1732
|
+
parsedOrigin = new URL(origin);
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
const cleanOrigin = parsedOrigin.origin;
|
|
1737
|
+
if (clientUrl && cleanOrigin === clientUrl) {
|
|
1738
|
+
return cleanOrigin;
|
|
1739
|
+
}
|
|
1740
|
+
const extra = options.extraAllowedOrigins ?? envExtraOrigins();
|
|
1741
|
+
if (extra.includes(cleanOrigin)) {
|
|
1742
|
+
return cleanOrigin;
|
|
1743
|
+
}
|
|
1744
|
+
if (options.allowedPatterns?.some((pattern) => pattern.test(cleanOrigin))) {
|
|
1745
|
+
return cleanOrigin;
|
|
1746
|
+
}
|
|
1747
|
+
if ((options.allowCodespaces ?? true) && CODESPACE_REGEX.test(cleanOrigin)) {
|
|
1748
|
+
return cleanOrigin;
|
|
1749
|
+
}
|
|
1750
|
+
if ((options.allowLocalhostInDev ?? true) && process.env.NODE_ENV !== "production" && LOCALHOST_REGEX.test(cleanOrigin)) {
|
|
1751
|
+
return cleanOrigin;
|
|
1752
|
+
}
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/server/auth/mock.ts
|
|
1757
|
+
var DEFAULT_MOCK_PATH_PREFIX = "/auth/mock/provider";
|
|
1758
|
+
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
1759
|
+
var TOKEN_TTL_MS = 60 * 60 * 1e3;
|
|
1760
|
+
var DEV_PRIVATE_ORIGIN_REGEX = /^http:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}):\d+$/;
|
|
1761
|
+
function isMockOAuthEnabled() {
|
|
1762
|
+
return process.env.ENABLE_MOCK_OAUTH === "true" && process.env.NODE_ENV !== "production";
|
|
1763
|
+
}
|
|
1764
|
+
function createMockOAuthProvider(apiBaseUrl, options = {}) {
|
|
1765
|
+
const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;
|
|
1766
|
+
const publicBase = apiBaseUrl.replace(/\/$/, "");
|
|
1767
|
+
const internalBase = (options.internalBaseUrl ?? apiBaseUrl).replace(/\/$/, "");
|
|
1768
|
+
return {
|
|
1769
|
+
name: "mock",
|
|
1770
|
+
authorizationUrl: `${publicBase}${prefix}/authorize`,
|
|
1771
|
+
tokenUrl: `${internalBase}${prefix}/token`,
|
|
1772
|
+
userInfoUrl: `${internalBase}${prefix}/userinfo`,
|
|
1773
|
+
scopes: ["openid", "email", "profile"],
|
|
1774
|
+
parseUserInfo: (data) => {
|
|
1775
|
+
const d = data;
|
|
1776
|
+
return {
|
|
1777
|
+
id: String(d.id),
|
|
1778
|
+
email: String(d.email),
|
|
1779
|
+
name: String(d.name),
|
|
1780
|
+
picture: d.picture ? String(d.picture) : null,
|
|
1781
|
+
verified_email: true
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
function getQueryParam(req, name) {
|
|
1787
|
+
const fromQuery = req.query?.[name];
|
|
1788
|
+
if (typeof fromQuery === "string") return fromQuery;
|
|
1789
|
+
const url = req.originalUrl ?? req.url;
|
|
1790
|
+
if (!url) return null;
|
|
1791
|
+
const idx = url.indexOf("?");
|
|
1792
|
+
if (idx === -1) return null;
|
|
1793
|
+
return new URLSearchParams(url.slice(idx + 1)).get(name);
|
|
1794
|
+
}
|
|
1795
|
+
async function readBodyParams(req) {
|
|
1796
|
+
if (typeof req.body === "string") return new URLSearchParams(req.body);
|
|
1797
|
+
if (req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
|
|
1798
|
+
return new URLSearchParams(req.body);
|
|
1799
|
+
}
|
|
1800
|
+
const chunks = [];
|
|
1801
|
+
for await (const chunk of req) {
|
|
1802
|
+
chunks.push(Buffer.from(chunk));
|
|
1803
|
+
}
|
|
1804
|
+
return new URLSearchParams(Buffer.concat(chunks).toString("utf8"));
|
|
1805
|
+
}
|
|
1806
|
+
function escapeHtml(value) {
|
|
1807
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1808
|
+
}
|
|
1809
|
+
function isAllowedRedirectUri(uri, allowedOrigins) {
|
|
1810
|
+
let parsed;
|
|
1811
|
+
try {
|
|
1812
|
+
parsed = new URL(uri);
|
|
1813
|
+
} catch {
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1816
|
+
const origin = parsed.origin;
|
|
1817
|
+
if (allowedOrigins.includes(origin)) return true;
|
|
1818
|
+
if (DEV_PRIVATE_ORIGIN_REGEX.test(origin)) return true;
|
|
1819
|
+
return validateRedirectOrigin(origin) !== null;
|
|
1820
|
+
}
|
|
1821
|
+
function renderPickerPage(users, authorizePath, redirectUri, state) {
|
|
1822
|
+
const links = users.map((user) => {
|
|
1823
|
+
const params = new URLSearchParams({ redirect_uri: redirectUri, email: user.email });
|
|
1824
|
+
if (state) params.set("state", state);
|
|
1825
|
+
const href = `${authorizePath}?${params.toString()}`;
|
|
1826
|
+
return `<li><a href="${escapeHtml(href)}"><strong>${escapeHtml(user.name)}</strong><span>${escapeHtml(user.email)}</span></a></li>`;
|
|
1827
|
+
}).join("\n");
|
|
1828
|
+
return `<!doctype html>
|
|
1829
|
+
<html lang="en">
|
|
1830
|
+
<head>
|
|
1831
|
+
<meta charset="utf-8" />
|
|
1832
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1833
|
+
<title>Mock sign-in</title>
|
|
1834
|
+
<style>
|
|
1835
|
+
body { font-family: system-ui, sans-serif; background: #111; color: #eee; display: flex; min-height: 100vh; align-items: center; justify-content: center; margin: 0; }
|
|
1836
|
+
main { max-width: 420px; width: 100%; padding: 2rem; }
|
|
1837
|
+
h1 { font-size: 1.25rem; }
|
|
1838
|
+
p { color: #999; font-size: 0.875rem; }
|
|
1839
|
+
ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1840
|
+
a { display: flex; flex-direction: column; gap: 0.125rem; padding: 0.75rem 1rem; border: 1px solid #333; border-radius: 8px; text-decoration: none; color: inherit; }
|
|
1841
|
+
a:hover { border-color: #888; background: #1a1a1a; }
|
|
1842
|
+
a span { color: #999; font-size: 0.8125rem; }
|
|
1843
|
+
</style>
|
|
1844
|
+
</head>
|
|
1845
|
+
<body>
|
|
1846
|
+
<main>
|
|
1847
|
+
<h1>Mock sign-in</h1>
|
|
1848
|
+
<p>Development only \u2014 pick an account to continue. Run your seed script if this list is empty.</p>
|
|
1849
|
+
<ul>
|
|
1850
|
+
${links || "<li><em>No users found.</em></li>"}
|
|
1851
|
+
</ul>
|
|
1852
|
+
</main>
|
|
1853
|
+
</body>
|
|
1854
|
+
</html>`;
|
|
1855
|
+
}
|
|
1856
|
+
function registerMockOAuthProvider(router, options) {
|
|
1857
|
+
const logger = options.logger ?? consoleLogger;
|
|
1858
|
+
if (!isMockOAuthEnabled()) {
|
|
1859
|
+
if (process.env.ENABLE_MOCK_OAUTH === "true") {
|
|
1860
|
+
logger.error("Mock OAuth is enabled but NODE_ENV is production \u2014 refusing to mount routes");
|
|
1861
|
+
}
|
|
1862
|
+
return false;
|
|
1863
|
+
}
|
|
1864
|
+
const prefix = options.pathPrefix ?? DEFAULT_MOCK_PATH_PREFIX;
|
|
1865
|
+
const allowedOrigins = options.allowedRedirectOrigins ?? [];
|
|
1866
|
+
const codes = /* @__PURE__ */ new Map();
|
|
1867
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
1868
|
+
function sweep(store) {
|
|
1869
|
+
const now = Date.now();
|
|
1870
|
+
for (const [key, entry] of store) {
|
|
1871
|
+
if (entry.expiresAt < now) store.delete(key);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
function guard(res) {
|
|
1875
|
+
if (process.env.NODE_ENV === "production") {
|
|
1876
|
+
res.status(404).json({ error: "Not found" });
|
|
1877
|
+
return false;
|
|
1878
|
+
}
|
|
1879
|
+
return true;
|
|
1880
|
+
}
|
|
1881
|
+
router.get(`${prefix}/authorize`, async (req, res) => {
|
|
1882
|
+
if (!guard(res)) return;
|
|
1883
|
+
const redirectUri = getQueryParam(req, "redirect_uri");
|
|
1884
|
+
const state = getQueryParam(req, "state");
|
|
1885
|
+
const email = getQueryParam(req, "email");
|
|
1886
|
+
if (!redirectUri || !isAllowedRedirectUri(redirectUri, allowedOrigins)) {
|
|
1887
|
+
res.status(400).json({ error: "Invalid redirect_uri" });
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const users = await options.listUsers();
|
|
1891
|
+
if (!email) {
|
|
1892
|
+
res.status(200).send(renderPickerPage(users, `${prefix}/authorize`, redirectUri, state));
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const user = users.find((u) => u.email === email);
|
|
1896
|
+
if (!user) {
|
|
1897
|
+
res.status(400).json({ error: "Unknown mock user" });
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
sweep(codes);
|
|
1901
|
+
const code = randomBytes(24).toString("hex");
|
|
1902
|
+
codes.set(code, { email: user.email, expiresAt: Date.now() + CODE_TTL_MS });
|
|
1903
|
+
const params = new URLSearchParams({ code });
|
|
1904
|
+
if (state) params.set("state", state);
|
|
1905
|
+
res.redirect(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}${params.toString()}`);
|
|
1906
|
+
});
|
|
1907
|
+
router.post(`${prefix}/token`, async (req, res) => {
|
|
1908
|
+
if (!guard(res)) return;
|
|
1909
|
+
const body = await readBodyParams(req);
|
|
1910
|
+
const code = body.get("code");
|
|
1911
|
+
const entry = code ? codes.get(code) : void 0;
|
|
1912
|
+
if (code) codes.delete(code);
|
|
1913
|
+
if (!entry || entry.expiresAt < Date.now()) {
|
|
1914
|
+
res.status(400).json({ error: "invalid_grant" });
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
sweep(tokens);
|
|
1918
|
+
const accessToken = randomBytes(24).toString("hex");
|
|
1919
|
+
tokens.set(accessToken, { email: entry.email, expiresAt: Date.now() + TOKEN_TTL_MS });
|
|
1920
|
+
res.status(200).json({
|
|
1921
|
+
access_token: accessToken,
|
|
1922
|
+
token_type: "Bearer",
|
|
1923
|
+
expires_in: Math.floor(TOKEN_TTL_MS / 1e3),
|
|
1924
|
+
scope: "openid email profile"
|
|
1925
|
+
});
|
|
1926
|
+
});
|
|
1927
|
+
router.get(`${prefix}/userinfo`, async (req, res) => {
|
|
1928
|
+
if (!guard(res)) return;
|
|
1929
|
+
const auth = req.headers.authorization;
|
|
1930
|
+
const accessToken = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : null;
|
|
1931
|
+
const entry = accessToken ? tokens.get(accessToken) : void 0;
|
|
1932
|
+
if (!entry || entry.expiresAt < Date.now()) {
|
|
1933
|
+
res.status(401).json({ error: "invalid_token" });
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const users = await options.listUsers();
|
|
1937
|
+
const user = users.find((u) => u.email === entry.email);
|
|
1938
|
+
if (!user) {
|
|
1939
|
+
res.status(401).json({ error: "invalid_token" });
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
res.status(200).json({
|
|
1943
|
+
// providerAccountId = email: stable across re-seeds, unlike row ids.
|
|
1944
|
+
id: user.email,
|
|
1945
|
+
email: user.email,
|
|
1946
|
+
name: user.name,
|
|
1947
|
+
picture: user.picture ?? null,
|
|
1948
|
+
verified_email: true
|
|
1949
|
+
});
|
|
1950
|
+
});
|
|
1951
|
+
logger.info(`Mock OAuth provider mounted at ${prefix} (development only)`);
|
|
1952
|
+
return true;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/server/auth/sessionCookie.ts
|
|
1956
|
+
var SESSION_COOKIE = "session";
|
|
1957
|
+
var DEFAULT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1958
|
+
function cookieOptions(options) {
|
|
1959
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
1960
|
+
const domain = options.domain ?? process.env.COOKIE_DOMAIN;
|
|
1961
|
+
return {
|
|
1962
|
+
httpOnly: true,
|
|
1963
|
+
secure: isProd,
|
|
1964
|
+
// SameSite=None (with Secure) lets the session cookie ride cross-site
|
|
1965
|
+
// fetches, so a secondary web origin can hit the primary API host. The
|
|
1966
|
+
// CORS allowlist (validateRedirectOrigin) is the actual origin gate. Dev
|
|
1967
|
+
// keeps Lax — localhost is http-only and SameSite=None requires Secure.
|
|
1968
|
+
sameSite: isProd ? "none" : "lax",
|
|
1969
|
+
path: "/",
|
|
1970
|
+
...domain && { domain }
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
function setSessionCookie(res, jwt, options = {}) {
|
|
1974
|
+
res.cookie(options.cookieName ?? SESSION_COOKIE, jwt, {
|
|
1975
|
+
...cookieOptions(options),
|
|
1976
|
+
maxAge: options.maxAgeMs ?? DEFAULT_MAX_AGE_MS
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
function clearSessionCookie(res, options = {}) {
|
|
1980
|
+
res.clearCookie(options.cookieName ?? SESSION_COOKIE, cookieOptions(options));
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/server/auth/restMiddleware.ts
|
|
1984
|
+
function extractBearerOrCookieToken(req, cookieName = SESSION_COOKIE) {
|
|
1985
|
+
const cookieToken = req.cookies?.[cookieName];
|
|
1986
|
+
if (cookieToken) return cookieToken;
|
|
1987
|
+
const authHeader = req.headers.authorization;
|
|
1988
|
+
if (authHeader?.startsWith("Bearer ")) return authHeader.slice(7);
|
|
1989
|
+
return null;
|
|
1990
|
+
}
|
|
1991
|
+
function createRequireAuth(options) {
|
|
1992
|
+
return async (req, res, next) => {
|
|
1993
|
+
const token = extractBearerOrCookieToken(req, options.cookieName);
|
|
1994
|
+
if (!token) {
|
|
1995
|
+
res.status(401).json({ error: "Missing or invalid authorization" });
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
const secret = options.jwtSecret ?? process.env.JWT_SECRET;
|
|
1999
|
+
if (!secret) {
|
|
2000
|
+
res.status(401).json({ error: "Authentication is not configured" });
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const payload = await verifyJWT(token, secret);
|
|
2004
|
+
if (!payload?.userId) {
|
|
2005
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const session = await options.getSession(token);
|
|
2009
|
+
if (!session || session.expiresAt < /* @__PURE__ */ new Date()) {
|
|
2010
|
+
res.status(401).json({ error: "Session expired or revoked" });
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
req.userId = payload.userId;
|
|
2014
|
+
next();
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1792
2018
|
// src/server/utils/env.ts
|
|
1793
2019
|
function validateEnv(options) {
|
|
1794
2020
|
const result = checkEnv(options);
|
|
@@ -1797,7 +2023,11 @@ function validateEnv(options) {
|
|
|
1797
2023
|
}
|
|
1798
2024
|
}
|
|
1799
2025
|
function checkEnv(options) {
|
|
1800
|
-
const {
|
|
2026
|
+
const {
|
|
2027
|
+
required,
|
|
2028
|
+
productionOnly = true,
|
|
2029
|
+
errorPrefix = "Missing required environment variables"
|
|
2030
|
+
} = options;
|
|
1801
2031
|
if (productionOnly && process.env.NODE_ENV !== "production") {
|
|
1802
2032
|
return { success: true, missing: [] };
|
|
1803
2033
|
}
|
|
@@ -1827,6 +2057,54 @@ function requireEnv(name, defaultValue) {
|
|
|
1827
2057
|
}
|
|
1828
2058
|
return value;
|
|
1829
2059
|
}
|
|
2060
|
+
var ALGORITHM = "aes-256-gcm";
|
|
2061
|
+
var IV_LENGTH = 12;
|
|
2062
|
+
var AUTH_TAG_LENGTH = 16;
|
|
2063
|
+
function getKey() {
|
|
2064
|
+
const hex = process.env.ENCRYPTION_KEY;
|
|
2065
|
+
if (!hex || hex.length !== 64) {
|
|
2066
|
+
throw new Error(
|
|
2067
|
+
"ENCRYPTION_KEY must be set as a 64-character hex string (32 bytes). Check your environment configuration."
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
return Buffer.from(hex, "hex");
|
|
2071
|
+
}
|
|
2072
|
+
function encrypt(plaintext) {
|
|
2073
|
+
const key = getKey();
|
|
2074
|
+
const iv = randomBytes(IV_LENGTH);
|
|
2075
|
+
const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
2076
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2077
|
+
const authTag = cipher.getAuthTag();
|
|
2078
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
2079
|
+
}
|
|
2080
|
+
function decrypt(ciphertext) {
|
|
2081
|
+
const key = getKey();
|
|
2082
|
+
const [ivHex, authTagHex, encryptedHex] = ciphertext.split(":");
|
|
2083
|
+
if (!ivHex || !authTagHex || !encryptedHex) {
|
|
2084
|
+
throw new Error("Invalid ciphertext format");
|
|
2085
|
+
}
|
|
2086
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
2087
|
+
const authTag = Buffer.from(authTagHex, "hex");
|
|
2088
|
+
const encrypted = Buffer.from(encryptedHex, "hex");
|
|
2089
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
2090
|
+
decipher.setAuthTag(authTag);
|
|
2091
|
+
return decipher.update(encrypted) + decipher.final("utf8");
|
|
2092
|
+
}
|
|
2093
|
+
function isEncrypted(value) {
|
|
2094
|
+
const parts = value.split(":");
|
|
2095
|
+
return parts.length === 3 && parts.every((p) => /^[0-9a-f]+$/.test(p));
|
|
2096
|
+
}
|
|
2097
|
+
function decryptIfEncrypted(value) {
|
|
2098
|
+
return isEncrypted(value) ? decrypt(value) : value;
|
|
2099
|
+
}
|
|
2100
|
+
function tryDecrypt(ciphertext, logger = consoleLogger) {
|
|
2101
|
+
try {
|
|
2102
|
+
return decryptIfEncrypted(ciphertext);
|
|
2103
|
+
} catch {
|
|
2104
|
+
logger.warn("Failed to decrypt stored token \u2014 likely stale or re-keyed");
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
1830
2108
|
|
|
1831
2109
|
// src/server/mcp/McpRegistry.ts
|
|
1832
2110
|
var McpRegistry = class {
|
|
@@ -1863,7 +2141,9 @@ var McpRegistry = class {
|
|
|
1863
2141
|
listTools(options) {
|
|
1864
2142
|
const hasDefaultUser = !!options?.defaultUserId;
|
|
1865
2143
|
if (this.customToolSpecs) {
|
|
1866
|
-
return this.customToolSpecs.filter((s) => this.services.has(s.name)).map(
|
|
2144
|
+
return this.customToolSpecs.filter((s) => this.services.has(s.name)).map(
|
|
2145
|
+
(svc) => this.buildToolDefinition(svc.name, svc.description, svc.methodNames, hasDefaultUser)
|
|
2146
|
+
);
|
|
1867
2147
|
}
|
|
1868
2148
|
const tools = [];
|
|
1869
2149
|
for (const [serviceName] of this.services) {
|
|
@@ -1898,11 +2178,7 @@ var McpRegistry = class {
|
|
|
1898
2178
|
const startTime = Date.now();
|
|
1899
2179
|
try {
|
|
1900
2180
|
const validatedPayload = this.validatePayload(method, payload);
|
|
1901
|
-
await instance.ensureAccessForMethod(
|
|
1902
|
-
method.access,
|
|
1903
|
-
ctx,
|
|
1904
|
-
entryId
|
|
1905
|
-
);
|
|
2181
|
+
await instance.ensureAccessForMethod(method.access, ctx, entryId);
|
|
1906
2182
|
const result = await method.handler(validatedPayload, {
|
|
1907
2183
|
userId,
|
|
1908
2184
|
socketId: ctx.id,
|
|
@@ -1954,9 +2230,7 @@ var McpRegistry = class {
|
|
|
1954
2230
|
if (method.schema) {
|
|
1955
2231
|
const result = method.schema.safeParse(payload);
|
|
1956
2232
|
if (!result.success) {
|
|
1957
|
-
throw new Error(
|
|
1958
|
-
`Validation error: ${result.error?.message ?? "invalid payload"}`
|
|
1959
|
-
);
|
|
2233
|
+
throw new Error(`Validation error: ${result.error?.message ?? "invalid payload"}`);
|
|
1960
2234
|
}
|
|
1961
2235
|
return result.data;
|
|
1962
2236
|
}
|
|
@@ -2053,12 +2327,7 @@ function createMcpStdioServer(options) {
|
|
|
2053
2327
|
}
|
|
2054
2328
|
const userId = args?.userId ?? defaultUserId ?? process.env.MCP_USER_ID ?? "mcp-anonymous";
|
|
2055
2329
|
const payload = args?.payload;
|
|
2056
|
-
const result = await registry.invoke(
|
|
2057
|
-
serviceName,
|
|
2058
|
-
methodName,
|
|
2059
|
-
payload,
|
|
2060
|
-
userId
|
|
2061
|
-
);
|
|
2330
|
+
const result = await registry.invoke(serviceName, methodName, payload, userId);
|
|
2062
2331
|
return {
|
|
2063
2332
|
jsonrpc: "2.0",
|
|
2064
2333
|
id,
|
|
@@ -2181,12 +2450,7 @@ function createMcpRoutes(router, options) {
|
|
|
2181
2450
|
res.status(400).json({ error: "Missing service or method" });
|
|
2182
2451
|
return;
|
|
2183
2452
|
}
|
|
2184
|
-
const result = await registry.invoke(
|
|
2185
|
-
service,
|
|
2186
|
-
method,
|
|
2187
|
-
toolPayload,
|
|
2188
|
-
tokenPayload.userId
|
|
2189
|
-
);
|
|
2453
|
+
const result = await registry.invoke(service, method, toolPayload, tokenPayload.userId);
|
|
2190
2454
|
res.json({ success: true, data: result });
|
|
2191
2455
|
} catch (error) {
|
|
2192
2456
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
@@ -2464,10 +2728,7 @@ function generateToolMetadata(options) {
|
|
|
2464
2728
|
allServices.push(serviceInfo);
|
|
2465
2729
|
if (docsOutputDir) {
|
|
2466
2730
|
const doc = generateServiceDoc(serviceInfo);
|
|
2467
|
-
const outputPath = path.join(
|
|
2468
|
-
docsOutputDir,
|
|
2469
|
-
`${serviceInfo.className}.md`
|
|
2470
|
-
);
|
|
2731
|
+
const outputPath = path.join(docsOutputDir, `${serviceInfo.className}.md`);
|
|
2471
2732
|
fs.writeFileSync(outputPath, doc);
|
|
2472
2733
|
}
|
|
2473
2734
|
}
|
|
@@ -2488,15 +2749,12 @@ Auto-generated documentation for all services.
|
|
|
2488
2749
|
}
|
|
2489
2750
|
const toolsContent = generateToolsFileContent(allServices);
|
|
2490
2751
|
fs.writeFileSync(toolsOutputPath, toolsContent);
|
|
2491
|
-
const totalMethods = allServices.reduce(
|
|
2492
|
-
(sum, s) => sum + s.methods.length,
|
|
2493
|
-
0
|
|
2494
|
-
);
|
|
2752
|
+
const totalMethods = allServices.reduce((sum, s) => sum + s.methods.length, 0);
|
|
2495
2753
|
console.log(
|
|
2496
2754
|
`Generated ${allServices.length} service tools (${totalMethods} methods) -> ${toolsOutputPath}`
|
|
2497
2755
|
);
|
|
2498
2756
|
}
|
|
2499
2757
|
|
|
2500
|
-
export { BaseService, McpRegistry, ServiceRegistry, applyRateLimitMiddleware, bootstrapMcpServer, checkEnv, createJWT, createMcpRoutes, createMcpStdioServer, createOAuthURL, createQuickdrawServer, createRateLimiter, createTieredRateLimiter, discordProvider, exchangeOAuthCode, generateToolMetadata, getDefaultEntityFields, googleProvider, isRedisAdapterAvailable, mergeWithDefaultFields, requireEnv, setupRedisAdapter, validateEnv, verifyJWT, zodToAdminFields };
|
|
2758
|
+
export { BaseService, DEFAULT_MOCK_PATH_PREFIX, McpRegistry, OAUTH_RETURN_ORIGIN_COOKIE, SESSION_COOKIE, ServiceRegistry, applyRateLimitMiddleware, bootstrapMcpServer, checkEnv, clearSessionCookie, createJWT, createMcpRoutes, createMcpStdioServer, createMockOAuthProvider, createOAuthURL, createQuickdrawServer, createRateLimiter, createRequireAuth, createTieredRateLimiter, decrypt, decryptIfEncrypted, discordProvider, encrypt, exchangeOAuthCode, extractBearerOrCookieToken, generateToolMetadata, getDefaultEntityFields, getDiscordAvatarUrl, googleProvider, isEncrypted, isMockOAuthEnabled, isRedisAdapterAvailable, mergeWithDefaultFields, registerMockOAuthProvider, requireEnv, setSessionCookie, setupRedisAdapter, tryDecrypt, validateEnv, validateRedirectOrigin, verifyJWT, zodToAdminFields };
|
|
2501
2759
|
//# sourceMappingURL=index.js.map
|
|
2502
2760
|
//# sourceMappingURL=index.js.map
|