@librechat/data-schemas 0.0.14 → 0.0.16
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 +318 -0
- package/dist/index.cjs +2668 -1041
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +2650 -1018
- package/dist/index.es.js.map +1 -1
- package/dist/types/common/enum.d.ts +13 -0
- package/dist/types/common/index.d.ts +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/methods/accessRole.d.ts +42 -0
- package/dist/types/methods/accessRole.spec.d.ts +1 -0
- package/dist/types/methods/aclEntry.d.ts +52 -0
- package/dist/types/methods/aclEntry.spec.d.ts +1 -0
- package/dist/types/methods/agentCategory.d.ts +48 -0
- package/dist/types/methods/group.d.ts +40 -0
- package/dist/types/methods/group.spec.d.ts +1 -0
- package/dist/types/methods/index.d.ts +116 -6
- package/dist/types/methods/pluginAuth.d.ts +1 -1
- package/dist/types/methods/user.d.ts +9 -1
- package/dist/types/methods/userGroup.d.ts +73 -0
- package/dist/types/methods/userGroup.methods.spec.d.ts +1 -0
- package/dist/types/methods/userGroup.roles.spec.d.ts +1 -0
- package/dist/types/methods/userGroup.spec.d.ts +1 -0
- package/dist/types/models/accessRole.d.ts +30 -0
- package/dist/types/models/aclEntry.d.ts +30 -0
- package/dist/types/models/agentCategory.d.ts +30 -0
- package/dist/types/models/group.d.ts +30 -0
- package/dist/types/models/index.d.ts +4 -0
- package/dist/types/schema/accessRole.d.ts +39 -0
- package/dist/types/schema/aclEntry.d.ts +39 -0
- package/dist/types/schema/agentCategory.d.ts +39 -0
- package/dist/types/schema/defaults.d.ts +4 -0
- package/dist/types/schema/group.d.ts +37 -0
- package/dist/types/schema/index.d.ts +2 -0
- package/dist/types/schema/preset.d.ts +1 -0
- package/dist/types/schema/prompt.d.ts +2 -9
- package/dist/types/schema/promptGroup.d.ts +4 -18
- package/dist/types/types/accessRole.d.ts +40 -0
- package/dist/types/types/aclEntry.d.ts +52 -0
- package/dist/types/types/agent.d.ts +8 -0
- package/dist/types/types/agentCategory.d.ts +41 -0
- package/dist/types/types/convo.d.ts +1 -0
- package/dist/types/types/group.d.ts +45 -0
- package/dist/types/types/index.d.ts +5 -0
- package/dist/types/types/pluginAuth.d.ts +1 -0
- package/dist/types/types/prompts.d.ts +50 -0
- package/dist/types/types/user.d.ts +2 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/object-traverse.d.ts +23 -0
- package/dist/types/utils/transactions.d.ts +14 -0
- package/package.json +1 -3
- package/dist/types/utils/traverse-wrapper.d.ts +0 -9
package/dist/index.es.js
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
+
import { PermissionBits, FileSources, Constants, PermissionTypes, Permissions, SystemRoles, ResourceType, PrincipalType, PrincipalModel, roleDefaults, AccessRoleIds } from 'librechat-data-provider';
|
|
1
2
|
import jwt from 'jsonwebtoken';
|
|
2
3
|
import { webcrypto } from 'node:crypto';
|
|
3
|
-
import mongoose, { Schema } from 'mongoose';
|
|
4
|
-
import { FileSources, Constants, PermissionTypes, Permissions, SystemRoles, roleDefaults } from 'librechat-data-provider';
|
|
5
|
-
import _ from 'lodash';
|
|
6
|
-
import { MeiliSearch } from 'meilisearch';
|
|
4
|
+
import mongoose, { Schema, Types } from 'mongoose';
|
|
7
5
|
import winston from 'winston';
|
|
8
6
|
import 'winston-daily-rotate-file';
|
|
9
|
-
import path from 'path';
|
|
10
7
|
import { klona } from 'klona';
|
|
11
|
-
import
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import _ from 'lodash';
|
|
10
|
+
import { MeiliSearch } from 'meilisearch';
|
|
12
11
|
import { nanoid } from 'nanoid';
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Common role combinations
|
|
15
|
+
*/
|
|
16
|
+
var RoleBits;
|
|
17
|
+
(function (RoleBits) {
|
|
18
|
+
/** 0001 = 1 */
|
|
19
|
+
RoleBits[RoleBits["VIEWER"] = PermissionBits.VIEW] = "VIEWER";
|
|
20
|
+
/** 0011 = 3 */
|
|
21
|
+
RoleBits[RoleBits["EDITOR"] = PermissionBits.VIEW | PermissionBits.EDIT] = "EDITOR";
|
|
22
|
+
/** 0111 = 7 */
|
|
23
|
+
RoleBits[RoleBits["MANAGER"] = PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE] = "MANAGER";
|
|
24
|
+
/** 1111 = 15 */
|
|
25
|
+
RoleBits[RoleBits["OWNER"] = PermissionBits.VIEW | PermissionBits.EDIT | PermissionBits.DELETE | PermissionBits.SHARE] = "OWNER";
|
|
26
|
+
})(RoleBits || (RoleBits = {}));
|
|
27
|
+
|
|
14
28
|
async function signPayload({ payload, secret, expirationTime, }) {
|
|
15
29
|
return jwt.sign(payload, secret, { expiresIn: expirationTime });
|
|
16
30
|
}
|
|
@@ -154,9 +168,59 @@ const agentSchema = new Schema({
|
|
|
154
168
|
type: [Schema.Types.Mixed],
|
|
155
169
|
default: [],
|
|
156
170
|
},
|
|
171
|
+
category: {
|
|
172
|
+
type: String,
|
|
173
|
+
trim: true,
|
|
174
|
+
index: true,
|
|
175
|
+
default: 'general',
|
|
176
|
+
},
|
|
177
|
+
support_contact: {
|
|
178
|
+
type: Schema.Types.Mixed,
|
|
179
|
+
default: undefined,
|
|
180
|
+
},
|
|
181
|
+
is_promoted: {
|
|
182
|
+
type: Boolean,
|
|
183
|
+
default: false,
|
|
184
|
+
index: true,
|
|
185
|
+
},
|
|
186
|
+
}, {
|
|
187
|
+
timestamps: true,
|
|
188
|
+
});
|
|
189
|
+
agentSchema.index({ updatedAt: -1, _id: 1 });
|
|
190
|
+
|
|
191
|
+
const agentCategorySchema = new Schema({
|
|
192
|
+
value: {
|
|
193
|
+
type: String,
|
|
194
|
+
required: true,
|
|
195
|
+
unique: true,
|
|
196
|
+
trim: true,
|
|
197
|
+
lowercase: true,
|
|
198
|
+
index: true,
|
|
199
|
+
},
|
|
200
|
+
label: {
|
|
201
|
+
type: String,
|
|
202
|
+
required: true,
|
|
203
|
+
trim: true,
|
|
204
|
+
},
|
|
205
|
+
description: {
|
|
206
|
+
type: String,
|
|
207
|
+
trim: true,
|
|
208
|
+
default: '',
|
|
209
|
+
},
|
|
210
|
+
order: {
|
|
211
|
+
type: Number,
|
|
212
|
+
default: 0,
|
|
213
|
+
index: true,
|
|
214
|
+
},
|
|
215
|
+
isActive: {
|
|
216
|
+
type: Boolean,
|
|
217
|
+
default: true,
|
|
218
|
+
index: true,
|
|
219
|
+
},
|
|
157
220
|
}, {
|
|
158
221
|
timestamps: true,
|
|
159
222
|
});
|
|
223
|
+
agentCategorySchema.index({ isActive: 1, order: 1 });
|
|
160
224
|
|
|
161
225
|
const assistantSchema = new Schema({
|
|
162
226
|
user: {
|
|
@@ -442,6 +506,10 @@ const conversationPreset = {
|
|
|
442
506
|
reasoning_summary: {
|
|
443
507
|
type: String,
|
|
444
508
|
},
|
|
509
|
+
/** Verbosity control */
|
|
510
|
+
verbosity: {
|
|
511
|
+
type: String,
|
|
512
|
+
},
|
|
445
513
|
};
|
|
446
514
|
|
|
447
515
|
const convoSchema = new Schema({
|
|
@@ -899,69 +967,56 @@ promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
|
|
|
899
967
|
*/
|
|
900
968
|
const rolePermissionsSchema = new Schema({
|
|
901
969
|
[PermissionTypes.BOOKMARKS]: {
|
|
902
|
-
[Permissions.USE]: { type: Boolean
|
|
970
|
+
[Permissions.USE]: { type: Boolean },
|
|
903
971
|
},
|
|
904
972
|
[PermissionTypes.PROMPTS]: {
|
|
905
|
-
[Permissions.SHARED_GLOBAL]: { type: Boolean
|
|
906
|
-
[Permissions.USE]: { type: Boolean
|
|
907
|
-
[Permissions.CREATE]: { type: Boolean
|
|
973
|
+
[Permissions.SHARED_GLOBAL]: { type: Boolean },
|
|
974
|
+
[Permissions.USE]: { type: Boolean },
|
|
975
|
+
[Permissions.CREATE]: { type: Boolean },
|
|
908
976
|
},
|
|
909
977
|
[PermissionTypes.MEMORIES]: {
|
|
910
|
-
[Permissions.USE]: { type: Boolean
|
|
911
|
-
[Permissions.CREATE]: { type: Boolean
|
|
912
|
-
[Permissions.UPDATE]: { type: Boolean
|
|
913
|
-
[Permissions.READ]: { type: Boolean
|
|
914
|
-
[Permissions.OPT_OUT]: { type: Boolean
|
|
978
|
+
[Permissions.USE]: { type: Boolean },
|
|
979
|
+
[Permissions.CREATE]: { type: Boolean },
|
|
980
|
+
[Permissions.UPDATE]: { type: Boolean },
|
|
981
|
+
[Permissions.READ]: { type: Boolean },
|
|
982
|
+
[Permissions.OPT_OUT]: { type: Boolean },
|
|
915
983
|
},
|
|
916
984
|
[PermissionTypes.AGENTS]: {
|
|
917
|
-
[Permissions.SHARED_GLOBAL]: { type: Boolean
|
|
918
|
-
[Permissions.USE]: { type: Boolean
|
|
919
|
-
[Permissions.CREATE]: { type: Boolean
|
|
985
|
+
[Permissions.SHARED_GLOBAL]: { type: Boolean },
|
|
986
|
+
[Permissions.USE]: { type: Boolean },
|
|
987
|
+
[Permissions.CREATE]: { type: Boolean },
|
|
920
988
|
},
|
|
921
989
|
[PermissionTypes.MULTI_CONVO]: {
|
|
922
|
-
[Permissions.USE]: { type: Boolean
|
|
990
|
+
[Permissions.USE]: { type: Boolean },
|
|
923
991
|
},
|
|
924
992
|
[PermissionTypes.TEMPORARY_CHAT]: {
|
|
925
|
-
[Permissions.USE]: { type: Boolean
|
|
993
|
+
[Permissions.USE]: { type: Boolean },
|
|
926
994
|
},
|
|
927
995
|
[PermissionTypes.RUN_CODE]: {
|
|
928
|
-
[Permissions.USE]: { type: Boolean
|
|
996
|
+
[Permissions.USE]: { type: Boolean },
|
|
929
997
|
},
|
|
930
998
|
[PermissionTypes.WEB_SEARCH]: {
|
|
931
|
-
[Permissions.USE]: { type: Boolean
|
|
999
|
+
[Permissions.USE]: { type: Boolean },
|
|
1000
|
+
},
|
|
1001
|
+
[PermissionTypes.PEOPLE_PICKER]: {
|
|
1002
|
+
[Permissions.VIEW_USERS]: { type: Boolean },
|
|
1003
|
+
[Permissions.VIEW_GROUPS]: { type: Boolean },
|
|
1004
|
+
[Permissions.VIEW_ROLES]: { type: Boolean },
|
|
1005
|
+
},
|
|
1006
|
+
[PermissionTypes.MARKETPLACE]: {
|
|
1007
|
+
[Permissions.USE]: { type: Boolean },
|
|
932
1008
|
},
|
|
933
1009
|
[PermissionTypes.FILE_SEARCH]: {
|
|
934
|
-
[Permissions.USE]: { type: Boolean
|
|
1010
|
+
[Permissions.USE]: { type: Boolean },
|
|
1011
|
+
},
|
|
1012
|
+
[PermissionTypes.FILE_CITATIONS]: {
|
|
1013
|
+
[Permissions.USE]: { type: Boolean },
|
|
935
1014
|
},
|
|
936
1015
|
}, { _id: false });
|
|
937
1016
|
const roleSchema = new Schema({
|
|
938
1017
|
name: { type: String, required: true, unique: true, index: true },
|
|
939
1018
|
permissions: {
|
|
940
1019
|
type: rolePermissionsSchema,
|
|
941
|
-
default: () => ({
|
|
942
|
-
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
|
943
|
-
[PermissionTypes.PROMPTS]: {
|
|
944
|
-
[Permissions.SHARED_GLOBAL]: false,
|
|
945
|
-
[Permissions.USE]: true,
|
|
946
|
-
[Permissions.CREATE]: true,
|
|
947
|
-
},
|
|
948
|
-
[PermissionTypes.MEMORIES]: {
|
|
949
|
-
[Permissions.USE]: true,
|
|
950
|
-
[Permissions.CREATE]: true,
|
|
951
|
-
[Permissions.UPDATE]: true,
|
|
952
|
-
[Permissions.READ]: true,
|
|
953
|
-
},
|
|
954
|
-
[PermissionTypes.AGENTS]: {
|
|
955
|
-
[Permissions.SHARED_GLOBAL]: false,
|
|
956
|
-
[Permissions.USE]: true,
|
|
957
|
-
[Permissions.CREATE]: true,
|
|
958
|
-
},
|
|
959
|
-
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
|
960
|
-
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
|
961
|
-
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
|
|
962
|
-
[PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true },
|
|
963
|
-
[PermissionTypes.FILE_SEARCH]: { [Permissions.USE]: true },
|
|
964
|
-
}),
|
|
965
1020
|
},
|
|
966
1021
|
});
|
|
967
1022
|
|
|
@@ -1151,6 +1206,7 @@ const userSchema = new Schema({
|
|
|
1151
1206
|
trim: true,
|
|
1152
1207
|
minlength: 8,
|
|
1153
1208
|
maxlength: 128,
|
|
1209
|
+
select: false,
|
|
1154
1210
|
},
|
|
1155
1211
|
avatar: {
|
|
1156
1212
|
type: String,
|
|
@@ -1214,6 +1270,7 @@ const userSchema = new Schema({
|
|
|
1214
1270
|
},
|
|
1215
1271
|
totpSecret: {
|
|
1216
1272
|
type: String,
|
|
1273
|
+
select: false,
|
|
1217
1274
|
},
|
|
1218
1275
|
backupCodes: {
|
|
1219
1276
|
type: [BackupCodeSchema],
|
|
@@ -1238,6 +1295,11 @@ const userSchema = new Schema({
|
|
|
1238
1295
|
},
|
|
1239
1296
|
default: {},
|
|
1240
1297
|
},
|
|
1298
|
+
/** Field for external source identification (for consistency with TPrincipal schema) */
|
|
1299
|
+
idOnTheSource: {
|
|
1300
|
+
type: String,
|
|
1301
|
+
sparse: true,
|
|
1302
|
+
},
|
|
1241
1303
|
}, { timestamps: true });
|
|
1242
1304
|
|
|
1243
1305
|
const MemoryEntrySchema = new Schema({
|
|
@@ -1269,96 +1331,478 @@ const MemoryEntrySchema = new Schema({
|
|
|
1269
1331
|
},
|
|
1270
1332
|
});
|
|
1271
1333
|
|
|
1334
|
+
const groupSchema = new Schema({
|
|
1335
|
+
name: {
|
|
1336
|
+
type: String,
|
|
1337
|
+
required: true,
|
|
1338
|
+
index: true,
|
|
1339
|
+
},
|
|
1340
|
+
description: {
|
|
1341
|
+
type: String,
|
|
1342
|
+
required: false,
|
|
1343
|
+
},
|
|
1344
|
+
email: {
|
|
1345
|
+
type: String,
|
|
1346
|
+
required: false,
|
|
1347
|
+
index: true,
|
|
1348
|
+
},
|
|
1349
|
+
avatar: {
|
|
1350
|
+
type: String,
|
|
1351
|
+
required: false,
|
|
1352
|
+
},
|
|
1353
|
+
memberIds: [
|
|
1354
|
+
{
|
|
1355
|
+
type: String,
|
|
1356
|
+
},
|
|
1357
|
+
],
|
|
1358
|
+
source: {
|
|
1359
|
+
type: String,
|
|
1360
|
+
enum: ['local', 'entra'],
|
|
1361
|
+
default: 'local',
|
|
1362
|
+
},
|
|
1363
|
+
/** External ID (e.g., Entra ID) */
|
|
1364
|
+
idOnTheSource: {
|
|
1365
|
+
type: String,
|
|
1366
|
+
sparse: true,
|
|
1367
|
+
index: true,
|
|
1368
|
+
required: function () {
|
|
1369
|
+
return this.source !== 'local';
|
|
1370
|
+
},
|
|
1371
|
+
},
|
|
1372
|
+
}, { timestamps: true });
|
|
1373
|
+
groupSchema.index({ idOnTheSource: 1, source: 1 }, {
|
|
1374
|
+
unique: true,
|
|
1375
|
+
partialFilterExpression: { idOnTheSource: { $exists: true } },
|
|
1376
|
+
});
|
|
1377
|
+
groupSchema.index({ memberIds: 1 });
|
|
1378
|
+
|
|
1272
1379
|
/**
|
|
1273
|
-
*
|
|
1380
|
+
* ESM-native object traversal utility
|
|
1381
|
+
* Simplified implementation focused on the forEach use case
|
|
1274
1382
|
*/
|
|
1275
|
-
function
|
|
1276
|
-
|
|
1383
|
+
function isObject(value) {
|
|
1384
|
+
if (value === null || typeof value !== 'object') {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
// Treat these built-in types as leaf nodes, not objects to traverse
|
|
1388
|
+
if (value instanceof Date)
|
|
1389
|
+
return false;
|
|
1390
|
+
if (value instanceof RegExp)
|
|
1391
|
+
return false;
|
|
1392
|
+
if (value instanceof Error)
|
|
1393
|
+
return false;
|
|
1394
|
+
if (value instanceof URL)
|
|
1395
|
+
return false;
|
|
1396
|
+
// Check for Buffer (Node.js)
|
|
1397
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
|
|
1398
|
+
return false;
|
|
1399
|
+
// Check for TypedArrays and ArrayBuffer
|
|
1400
|
+
if (ArrayBuffer.isView(value))
|
|
1401
|
+
return false;
|
|
1402
|
+
if (value instanceof ArrayBuffer)
|
|
1403
|
+
return false;
|
|
1404
|
+
if (value instanceof SharedArrayBuffer)
|
|
1405
|
+
return false;
|
|
1406
|
+
// Check for other built-in types that shouldn't be traversed
|
|
1407
|
+
if (value instanceof Promise)
|
|
1408
|
+
return false;
|
|
1409
|
+
if (value instanceof WeakMap)
|
|
1410
|
+
return false;
|
|
1411
|
+
if (value instanceof WeakSet)
|
|
1412
|
+
return false;
|
|
1413
|
+
if (value instanceof Map)
|
|
1414
|
+
return false;
|
|
1415
|
+
if (value instanceof Set)
|
|
1416
|
+
return false;
|
|
1417
|
+
// Check if it's a primitive wrapper object
|
|
1418
|
+
const stringTag = Object.prototype.toString.call(value);
|
|
1419
|
+
if (stringTag === '[object Boolean]' ||
|
|
1420
|
+
stringTag === '[object Number]' ||
|
|
1421
|
+
stringTag === '[object String]') {
|
|
1422
|
+
return false;
|
|
1423
|
+
}
|
|
1424
|
+
return true;
|
|
1425
|
+
}
|
|
1426
|
+
// Helper to safely set a property on an object or array
|
|
1427
|
+
function setProperty(obj, key, value) {
|
|
1428
|
+
if (Array.isArray(obj) && typeof key === 'number') {
|
|
1429
|
+
obj[key] = value;
|
|
1430
|
+
}
|
|
1431
|
+
else if (!Array.isArray(obj) && typeof key === 'string') {
|
|
1432
|
+
obj[key] = value;
|
|
1433
|
+
}
|
|
1434
|
+
else if (!Array.isArray(obj) && typeof key === 'number') {
|
|
1435
|
+
// Handle numeric keys on objects
|
|
1436
|
+
obj[key] = value;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
// Helper to safely delete a property from an object
|
|
1440
|
+
function deleteProperty(obj, key) {
|
|
1441
|
+
if (Array.isArray(obj) && typeof key === 'number') {
|
|
1442
|
+
// For arrays, we should use splice, but this is handled in remove()
|
|
1443
|
+
// This function is only called for non-array deletion
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (!Array.isArray(obj)) {
|
|
1447
|
+
delete obj[key];
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function forEach(obj, callback) {
|
|
1451
|
+
const visited = new WeakSet();
|
|
1452
|
+
function walk(node, path = [], parent) {
|
|
1453
|
+
// Check for circular references
|
|
1454
|
+
let circular = null;
|
|
1455
|
+
if (isObject(node)) {
|
|
1456
|
+
if (visited.has(node)) {
|
|
1457
|
+
// Find the circular reference in the parent chain
|
|
1458
|
+
let p = parent;
|
|
1459
|
+
while (p) {
|
|
1460
|
+
if (p.node === node) {
|
|
1461
|
+
circular = p;
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
p = p.parent;
|
|
1465
|
+
}
|
|
1466
|
+
return; // Skip circular references
|
|
1467
|
+
}
|
|
1468
|
+
visited.add(node);
|
|
1469
|
+
}
|
|
1470
|
+
const key = path.length > 0 ? path[path.length - 1] : undefined;
|
|
1471
|
+
const isRoot = path.length === 0;
|
|
1472
|
+
const level = path.length;
|
|
1473
|
+
// Determine if this is a leaf node
|
|
1474
|
+
const isLeaf = !isObject(node) ||
|
|
1475
|
+
(Array.isArray(node) && node.length === 0) ||
|
|
1476
|
+
Object.keys(node).length === 0;
|
|
1477
|
+
// Create context
|
|
1478
|
+
const context = {
|
|
1479
|
+
node,
|
|
1480
|
+
path: [...path],
|
|
1481
|
+
parent,
|
|
1482
|
+
key,
|
|
1483
|
+
isLeaf,
|
|
1484
|
+
notLeaf: !isLeaf,
|
|
1485
|
+
isRoot,
|
|
1486
|
+
notRoot: !isRoot,
|
|
1487
|
+
level,
|
|
1488
|
+
circular,
|
|
1489
|
+
update(value) {
|
|
1490
|
+
if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
|
|
1491
|
+
setProperty(parent.node, key, value);
|
|
1492
|
+
}
|
|
1493
|
+
this.node = value;
|
|
1494
|
+
},
|
|
1495
|
+
remove() {
|
|
1496
|
+
if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
|
|
1497
|
+
if (Array.isArray(parent.node) && typeof key === 'number') {
|
|
1498
|
+
parent.node.splice(key, 1);
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
deleteProperty(parent.node, key);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
};
|
|
1506
|
+
// Call the callback with the context
|
|
1507
|
+
callback.call(context, node);
|
|
1508
|
+
// Traverse children if not circular and is an object
|
|
1509
|
+
if (!circular && isObject(node) && !isLeaf) {
|
|
1510
|
+
if (Array.isArray(node)) {
|
|
1511
|
+
for (let i = 0; i < node.length; i++) {
|
|
1512
|
+
walk(node[i], [...path, i], context);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
for (const [childKey, childValue] of Object.entries(node)) {
|
|
1517
|
+
walk(childValue, [...path, childKey], context);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
walk(obj);
|
|
1523
|
+
}
|
|
1524
|
+
// Main traverse function that returns an object with forEach method
|
|
1525
|
+
function traverse(obj) {
|
|
1526
|
+
return {
|
|
1527
|
+
forEach(callback) {
|
|
1528
|
+
forEach(obj, callback);
|
|
1529
|
+
},
|
|
1530
|
+
};
|
|
1277
1531
|
}
|
|
1278
1532
|
|
|
1533
|
+
const SPLAT_SYMBOL = Symbol.for('splat');
|
|
1534
|
+
const MESSAGE_SYMBOL = Symbol.for('message');
|
|
1535
|
+
const CONSOLE_JSON_STRING_LENGTH = parseInt(process.env.CONSOLE_JSON_STRING_LENGTH || '', 10) || 255;
|
|
1536
|
+
const sensitiveKeys = [
|
|
1537
|
+
/^(sk-)[^\s]+/, // OpenAI API key pattern
|
|
1538
|
+
/(Bearer )[^\s]+/, // Header: Bearer token pattern
|
|
1539
|
+
/(api-key:? )[^\s]+/, // Header: API key pattern
|
|
1540
|
+
/(key=)[^\s]+/, // URL query param: sensitive key pattern (Google)
|
|
1541
|
+
];
|
|
1279
1542
|
/**
|
|
1280
|
-
*
|
|
1543
|
+
* Determines if a given value string is sensitive and returns matching regex patterns.
|
|
1544
|
+
*
|
|
1545
|
+
* @param valueStr - The value string to check.
|
|
1546
|
+
* @returns An array of regex patterns that match the value string.
|
|
1281
1547
|
*/
|
|
1282
|
-
function
|
|
1283
|
-
|
|
1548
|
+
function getMatchingSensitivePatterns(valueStr) {
|
|
1549
|
+
if (valueStr) {
|
|
1550
|
+
// Filter and return all regex patterns that match the value string
|
|
1551
|
+
return sensitiveKeys.filter((regex) => regex.test(valueStr));
|
|
1552
|
+
}
|
|
1553
|
+
return [];
|
|
1284
1554
|
}
|
|
1285
|
-
|
|
1286
1555
|
/**
|
|
1287
|
-
*
|
|
1556
|
+
* Redacts sensitive information from a console message and trims it to a specified length if provided.
|
|
1557
|
+
* @param str - The console message to be redacted.
|
|
1558
|
+
* @param trimLength - The optional length at which to trim the redacted message.
|
|
1559
|
+
* @returns The redacted and optionally trimmed console message.
|
|
1288
1560
|
*/
|
|
1289
|
-
function
|
|
1290
|
-
|
|
1561
|
+
function redactMessage(str, trimLength) {
|
|
1562
|
+
if (!str) {
|
|
1563
|
+
return '';
|
|
1564
|
+
}
|
|
1565
|
+
const patterns = getMatchingSensitivePatterns(str);
|
|
1566
|
+
patterns.forEach((pattern) => {
|
|
1567
|
+
str = str.replace(pattern, '$1[REDACTED]');
|
|
1568
|
+
});
|
|
1569
|
+
return str;
|
|
1291
1570
|
}
|
|
1292
|
-
|
|
1293
1571
|
/**
|
|
1294
|
-
*
|
|
1572
|
+
* Redacts sensitive information from log messages if the log level is 'error'.
|
|
1573
|
+
* Note: Intentionally mutates the object.
|
|
1574
|
+
* @param info - The log information object.
|
|
1575
|
+
* @returns The modified log information object.
|
|
1295
1576
|
*/
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1577
|
+
const redactFormat = winston.format((info) => {
|
|
1578
|
+
if (info.level === 'error') {
|
|
1579
|
+
// Type guard to ensure message is a string
|
|
1580
|
+
if (typeof info.message === 'string') {
|
|
1581
|
+
info.message = redactMessage(info.message);
|
|
1582
|
+
}
|
|
1583
|
+
// Handle MESSAGE_SYMBOL with type safety
|
|
1584
|
+
const symbolValue = info[MESSAGE_SYMBOL];
|
|
1585
|
+
if (typeof symbolValue === 'string') {
|
|
1586
|
+
info[MESSAGE_SYMBOL] = redactMessage(symbolValue);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
return info;
|
|
1590
|
+
});
|
|
1300
1591
|
/**
|
|
1301
|
-
*
|
|
1302
|
-
* Priority:
|
|
1303
|
-
* 1. LIBRECHAT_LOG_DIR environment variable
|
|
1304
|
-
* 2. If running within LibreChat monorepo (when cwd ends with /api), use api/logs
|
|
1305
|
-
* 3. If api/logs exists relative to cwd, use that (for running from project root)
|
|
1306
|
-
* 4. Otherwise, use logs directory relative to process.cwd()
|
|
1592
|
+
* Truncates long strings, especially base64 image data, within log messages.
|
|
1307
1593
|
*
|
|
1308
|
-
*
|
|
1594
|
+
* @param value - The value to be inspected and potentially truncated.
|
|
1595
|
+
* @param length - The length at which to truncate the value. Default: 100.
|
|
1596
|
+
* @returns The truncated or original value.
|
|
1309
1597
|
*/
|
|
1310
|
-
const
|
|
1311
|
-
if (
|
|
1312
|
-
return
|
|
1598
|
+
const truncateLongStrings = (value, length = 100) => {
|
|
1599
|
+
if (typeof value === 'string') {
|
|
1600
|
+
return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
|
|
1313
1601
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1602
|
+
return value;
|
|
1603
|
+
};
|
|
1604
|
+
/**
|
|
1605
|
+
* An array mapping function that truncates long strings (objects converted to JSON strings).
|
|
1606
|
+
* @param item - The item to be condensed.
|
|
1607
|
+
* @returns The condensed item.
|
|
1608
|
+
*/
|
|
1609
|
+
const condenseArray = (item) => {
|
|
1610
|
+
if (typeof item === 'string') {
|
|
1611
|
+
return truncateLongStrings(JSON.stringify(item));
|
|
1318
1612
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
const apiLogsPath = path.join(cwd, 'api', 'logs');
|
|
1322
|
-
// For LibreChat project structure, use api/logs
|
|
1323
|
-
// For external consumers, they should set LIBRECHAT_LOG_DIR
|
|
1324
|
-
if (cwd.includes('LibreChat')) {
|
|
1325
|
-
return apiLogsPath;
|
|
1613
|
+
else if (typeof item === 'object') {
|
|
1614
|
+
return truncateLongStrings(JSON.stringify(item));
|
|
1326
1615
|
}
|
|
1327
|
-
|
|
1328
|
-
return path.join(cwd, 'logs');
|
|
1329
|
-
};
|
|
1330
|
-
|
|
1331
|
-
const logDir$1 = getLogDirectory();
|
|
1332
|
-
const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1 = 'false' } = process.env;
|
|
1333
|
-
const useDebugLogging$1 = (typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true') ||
|
|
1334
|
-
DEBUG_LOGGING$1 === 'true';
|
|
1335
|
-
const levels$1 = {
|
|
1336
|
-
error: 0,
|
|
1337
|
-
warn: 1,
|
|
1338
|
-
info: 2,
|
|
1339
|
-
http: 3,
|
|
1340
|
-
verbose: 4,
|
|
1341
|
-
debug: 5,
|
|
1342
|
-
activity: 6,
|
|
1343
|
-
silly: 7,
|
|
1616
|
+
return item;
|
|
1344
1617
|
};
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1618
|
+
/**
|
|
1619
|
+
* Formats log messages for debugging purposes.
|
|
1620
|
+
* - Truncates long strings within log messages.
|
|
1621
|
+
* - Condenses arrays by truncating long strings and objects as strings within array items.
|
|
1622
|
+
* - Redacts sensitive information from log messages if the log level is 'error'.
|
|
1623
|
+
* - Converts log information object to a formatted string.
|
|
1624
|
+
*
|
|
1625
|
+
* @param options - The options for formatting log messages.
|
|
1626
|
+
* @returns The formatted log message.
|
|
1627
|
+
*/
|
|
1628
|
+
const debugTraverse = winston.format.printf(({ level, message, timestamp, ...metadata }) => {
|
|
1629
|
+
if (!message) {
|
|
1630
|
+
return `${timestamp} ${level}`;
|
|
1631
|
+
}
|
|
1632
|
+
// Type-safe version of the CJS logic: !message?.trim || typeof message !== 'string'
|
|
1633
|
+
if (typeof message !== 'string' || !message.trim) {
|
|
1634
|
+
return `${timestamp} ${level}: ${JSON.stringify(message)}`;
|
|
1635
|
+
}
|
|
1636
|
+
const msgParts = [
|
|
1637
|
+
`${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`,
|
|
1638
|
+
];
|
|
1639
|
+
try {
|
|
1640
|
+
if (level !== 'debug') {
|
|
1641
|
+
return msgParts[0];
|
|
1642
|
+
}
|
|
1643
|
+
if (!metadata) {
|
|
1644
|
+
return msgParts[0];
|
|
1645
|
+
}
|
|
1646
|
+
// Type-safe access to SPLAT_SYMBOL using bracket notation
|
|
1647
|
+
const metadataRecord = metadata;
|
|
1648
|
+
const splatArray = metadataRecord[SPLAT_SYMBOL];
|
|
1649
|
+
const debugValue = Array.isArray(splatArray) ? splatArray[0] : undefined;
|
|
1650
|
+
if (!debugValue) {
|
|
1651
|
+
return msgParts[0];
|
|
1652
|
+
}
|
|
1653
|
+
if (debugValue && Array.isArray(debugValue)) {
|
|
1654
|
+
msgParts.push(`\n${JSON.stringify(debugValue.map(condenseArray))}`);
|
|
1655
|
+
return msgParts.join('');
|
|
1656
|
+
}
|
|
1657
|
+
if (typeof debugValue !== 'object') {
|
|
1658
|
+
msgParts.push(` ${debugValue}`);
|
|
1659
|
+
return msgParts.join('');
|
|
1660
|
+
}
|
|
1661
|
+
msgParts.push('\n{');
|
|
1662
|
+
const copy = klona(metadata);
|
|
1663
|
+
try {
|
|
1664
|
+
const traversal = traverse(copy);
|
|
1665
|
+
traversal.forEach(function (value) {
|
|
1666
|
+
var _a;
|
|
1667
|
+
if (typeof (this === null || this === void 0 ? void 0 : this.key) === 'symbol') {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
let _parentKey = '';
|
|
1671
|
+
const parent = this.parent;
|
|
1672
|
+
if (typeof (parent === null || parent === void 0 ? void 0 : parent.key) !== 'symbol' && (parent === null || parent === void 0 ? void 0 : parent.key) !== undefined) {
|
|
1673
|
+
_parentKey = String(parent.key);
|
|
1674
|
+
}
|
|
1675
|
+
const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
|
|
1676
|
+
const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
|
|
1677
|
+
const currentKey = (_a = this === null || this === void 0 ? void 0 : this.key) !== null && _a !== void 0 ? _a : 'unknown';
|
|
1678
|
+
if (this.isLeaf && typeof value === 'string') {
|
|
1679
|
+
const truncatedText = truncateLongStrings(value);
|
|
1680
|
+
msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`);
|
|
1681
|
+
}
|
|
1682
|
+
else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
|
|
1683
|
+
const currentMessage = `\n${tabs}// ${value.length} ${String(currentKey).replace(/s$/, '')}(s)`;
|
|
1684
|
+
this.update(currentMessage);
|
|
1685
|
+
msgParts.push(currentMessage);
|
|
1686
|
+
const stringifiedArray = value.map(condenseArray);
|
|
1687
|
+
msgParts.push(`\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`);
|
|
1688
|
+
}
|
|
1689
|
+
else if (this.isLeaf && typeof value === 'function') {
|
|
1690
|
+
msgParts.push(`\n${tabs}${parentKey}${currentKey}: function,`);
|
|
1691
|
+
}
|
|
1692
|
+
else if (this.isLeaf) {
|
|
1693
|
+
msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${value},`);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
catch (e) {
|
|
1698
|
+
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
|
|
1699
|
+
msgParts.push(`\n[LOGGER TRAVERSAL ERROR] ${errorMessage}`);
|
|
1700
|
+
}
|
|
1701
|
+
msgParts.push('\n}');
|
|
1702
|
+
return msgParts.join('');
|
|
1703
|
+
}
|
|
1704
|
+
catch (e) {
|
|
1705
|
+
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
|
|
1706
|
+
msgParts.push(`\n[LOGGER PARSING ERROR] ${errorMessage}`);
|
|
1707
|
+
return msgParts.join('');
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
/**
|
|
1711
|
+
* Truncates long string values in JSON log objects.
|
|
1712
|
+
* Prevents outputting extremely long values (e.g., base64, blobs).
|
|
1713
|
+
*/
|
|
1714
|
+
const jsonTruncateFormat = winston.format((info) => {
|
|
1715
|
+
const truncateLongStrings = (str, maxLength) => str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
|
|
1716
|
+
const seen = new WeakSet();
|
|
1717
|
+
const truncateObject = (obj) => {
|
|
1718
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
1719
|
+
return obj;
|
|
1720
|
+
}
|
|
1721
|
+
// Handle circular references - now with proper object type
|
|
1722
|
+
if (seen.has(obj)) {
|
|
1723
|
+
return '[Circular]';
|
|
1724
|
+
}
|
|
1725
|
+
seen.add(obj);
|
|
1726
|
+
if (Array.isArray(obj)) {
|
|
1727
|
+
return obj.map((item) => truncateObject(item));
|
|
1728
|
+
}
|
|
1729
|
+
// We know this is an object at this point
|
|
1730
|
+
const objectRecord = obj;
|
|
1731
|
+
const newObj = {};
|
|
1732
|
+
Object.entries(objectRecord).forEach(([key, value]) => {
|
|
1733
|
+
if (typeof value === 'string') {
|
|
1734
|
+
newObj[key] = truncateLongStrings(value, CONSOLE_JSON_STRING_LENGTH);
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
newObj[key] = truncateObject(value);
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
return newObj;
|
|
1741
|
+
};
|
|
1742
|
+
return truncateObject(info);
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* Determine the log directory in a cross-compatible way.
|
|
1747
|
+
* Priority:
|
|
1748
|
+
* 1. LIBRECHAT_LOG_DIR environment variable
|
|
1749
|
+
* 2. If running within LibreChat monorepo (when cwd ends with /api), use api/logs
|
|
1750
|
+
* 3. If api/logs exists relative to cwd, use that (for running from project root)
|
|
1751
|
+
* 4. Otherwise, use logs directory relative to process.cwd()
|
|
1752
|
+
*
|
|
1753
|
+
* This avoids using __dirname which is not available in ESM modules
|
|
1754
|
+
*/
|
|
1755
|
+
const getLogDirectory = () => {
|
|
1756
|
+
if (process.env.LIBRECHAT_LOG_DIR) {
|
|
1757
|
+
return process.env.LIBRECHAT_LOG_DIR;
|
|
1758
|
+
}
|
|
1759
|
+
const cwd = process.cwd();
|
|
1760
|
+
// Check if we're running from within the api directory
|
|
1761
|
+
if (cwd.endsWith('/api') || cwd.endsWith('\\api')) {
|
|
1762
|
+
return path.join(cwd, 'logs');
|
|
1763
|
+
}
|
|
1764
|
+
// Check if api/logs exists relative to current directory (running from project root)
|
|
1765
|
+
// We'll just use the path and let the file system create it if needed
|
|
1766
|
+
const apiLogsPath = path.join(cwd, 'api', 'logs');
|
|
1767
|
+
// For LibreChat project structure, use api/logs
|
|
1768
|
+
// For external consumers, they should set LIBRECHAT_LOG_DIR
|
|
1769
|
+
if (cwd.includes('LibreChat')) {
|
|
1770
|
+
return apiLogsPath;
|
|
1771
|
+
}
|
|
1772
|
+
// Default to logs directory relative to current working directory
|
|
1773
|
+
return path.join(cwd, 'logs');
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
const logDir$1 = getLogDirectory();
|
|
1777
|
+
const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
|
|
1778
|
+
const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
|
|
1779
|
+
const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
|
|
1780
|
+
const useDebugLogging$1 = typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true';
|
|
1781
|
+
const levels$1 = {
|
|
1782
|
+
error: 0,
|
|
1783
|
+
warn: 1,
|
|
1784
|
+
info: 2,
|
|
1785
|
+
http: 3,
|
|
1786
|
+
verbose: 4,
|
|
1787
|
+
debug: 5,
|
|
1788
|
+
activity: 6,
|
|
1789
|
+
silly: 7,
|
|
1790
|
+
};
|
|
1791
|
+
winston.addColors({
|
|
1792
|
+
info: 'green',
|
|
1793
|
+
warn: 'italic yellow',
|
|
1794
|
+
error: 'red',
|
|
1349
1795
|
debug: 'blue',
|
|
1350
1796
|
});
|
|
1351
1797
|
const level$1 = () => {
|
|
1352
1798
|
const env = NODE_ENV$1 || 'development';
|
|
1353
|
-
|
|
1354
|
-
return isDevelopment ? 'debug' : 'warn';
|
|
1799
|
+
return env === 'development' ? 'debug' : 'warn';
|
|
1355
1800
|
};
|
|
1356
|
-
const fileFormat$1 = winston.format.combine(winston.format.timestamp({ format:
|
|
1357
|
-
const logLevel = useDebugLogging$1 ? 'debug' : 'error';
|
|
1801
|
+
const fileFormat$1 = winston.format.combine(redactFormat(), winston.format.timestamp({ format: () => new Date().toISOString() }), winston.format.errors({ stack: true }), winston.format.splat());
|
|
1358
1802
|
const transports$1 = [
|
|
1359
1803
|
new winston.transports.DailyRotateFile({
|
|
1360
|
-
level:
|
|
1361
|
-
filename: `${logDir$1}/
|
|
1804
|
+
level: 'error',
|
|
1805
|
+
filename: `${logDir$1}/error-%DATE%.log`,
|
|
1362
1806
|
datePattern: 'YYYY-MM-DD',
|
|
1363
1807
|
zippedArchive: true,
|
|
1364
1808
|
maxSize: '20m',
|
|
@@ -1366,17 +1810,174 @@ const transports$1 = [
|
|
|
1366
1810
|
format: fileFormat$1,
|
|
1367
1811
|
}),
|
|
1368
1812
|
];
|
|
1369
|
-
|
|
1370
|
-
transports$1.push(new winston.transports.
|
|
1371
|
-
|
|
1372
|
-
|
|
1813
|
+
if (useDebugLogging$1) {
|
|
1814
|
+
transports$1.push(new winston.transports.DailyRotateFile({
|
|
1815
|
+
level: 'debug',
|
|
1816
|
+
filename: `${logDir$1}/debug-%DATE%.log`,
|
|
1817
|
+
datePattern: 'YYYY-MM-DD',
|
|
1818
|
+
zippedArchive: true,
|
|
1819
|
+
maxSize: '20m',
|
|
1820
|
+
maxFiles: '14d',
|
|
1821
|
+
format: winston.format.combine(fileFormat$1, debugTraverse),
|
|
1822
|
+
}));
|
|
1823
|
+
}
|
|
1824
|
+
const consoleFormat$1 = winston.format.combine(redactFormat(), winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
|
|
1825
|
+
const message = `${info.timestamp} ${info.level}: ${info.message}`;
|
|
1826
|
+
return info.level.includes('error') ? redactMessage(message) : message;
|
|
1373
1827
|
}));
|
|
1828
|
+
let consoleLogLevel = 'info';
|
|
1829
|
+
if (useDebugConsole) {
|
|
1830
|
+
consoleLogLevel = 'debug';
|
|
1831
|
+
}
|
|
1832
|
+
// Add console transport
|
|
1833
|
+
if (useDebugConsole) {
|
|
1834
|
+
transports$1.push(new winston.transports.Console({
|
|
1835
|
+
level: consoleLogLevel,
|
|
1836
|
+
format: useConsoleJson
|
|
1837
|
+
? winston.format.combine(fileFormat$1, jsonTruncateFormat(), winston.format.json())
|
|
1838
|
+
: winston.format.combine(fileFormat$1, debugTraverse),
|
|
1839
|
+
}));
|
|
1840
|
+
}
|
|
1841
|
+
else if (useConsoleJson) {
|
|
1842
|
+
transports$1.push(new winston.transports.Console({
|
|
1843
|
+
level: consoleLogLevel,
|
|
1844
|
+
format: winston.format.combine(fileFormat$1, jsonTruncateFormat(), winston.format.json()),
|
|
1845
|
+
}));
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
transports$1.push(new winston.transports.Console({
|
|
1849
|
+
level: consoleLogLevel,
|
|
1850
|
+
format: consoleFormat$1,
|
|
1851
|
+
}));
|
|
1852
|
+
}
|
|
1853
|
+
// Create logger
|
|
1374
1854
|
const logger$1 = winston.createLogger({
|
|
1375
1855
|
level: level$1(),
|
|
1376
1856
|
levels: levels$1,
|
|
1377
1857
|
transports: transports$1,
|
|
1378
1858
|
});
|
|
1379
1859
|
|
|
1860
|
+
/**
|
|
1861
|
+
* Checks if the connected MongoDB deployment supports transactions
|
|
1862
|
+
* This requires a MongoDB replica set configuration
|
|
1863
|
+
*
|
|
1864
|
+
* @returns True if transactions are supported, false otherwise
|
|
1865
|
+
*/
|
|
1866
|
+
const supportsTransactions = async (mongoose) => {
|
|
1867
|
+
var _a;
|
|
1868
|
+
try {
|
|
1869
|
+
const session = await mongoose.startSession();
|
|
1870
|
+
try {
|
|
1871
|
+
session.startTransaction();
|
|
1872
|
+
await ((_a = mongoose.connection.db) === null || _a === void 0 ? void 0 : _a.collection('__transaction_test__').findOne({}, { session }));
|
|
1873
|
+
await session.abortTransaction();
|
|
1874
|
+
logger$1.debug('MongoDB transactions are supported');
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
catch (transactionError) {
|
|
1878
|
+
logger$1.debug('MongoDB transactions not supported (transaction error):', (transactionError === null || transactionError === void 0 ? void 0 : transactionError.message) || 'Unknown error');
|
|
1879
|
+
return false;
|
|
1880
|
+
}
|
|
1881
|
+
finally {
|
|
1882
|
+
await session.endSession();
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
catch (error) {
|
|
1886
|
+
logger$1.debug('MongoDB transactions not supported (session error):', (error === null || error === void 0 ? void 0 : error.message) || 'Unknown error');
|
|
1887
|
+
return false;
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
/**
|
|
1891
|
+
* Gets whether the current MongoDB deployment supports transactions
|
|
1892
|
+
* Caches the result for performance
|
|
1893
|
+
*
|
|
1894
|
+
* @returns True if transactions are supported, false otherwise
|
|
1895
|
+
*/
|
|
1896
|
+
const getTransactionSupport = async (mongoose, transactionSupportCache) => {
|
|
1897
|
+
let transactionsSupported = false;
|
|
1898
|
+
if (transactionSupportCache === null) {
|
|
1899
|
+
transactionsSupported = await supportsTransactions(mongoose);
|
|
1900
|
+
}
|
|
1901
|
+
return transactionsSupported;
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
/**
|
|
1905
|
+
* Creates or returns the User model using the provided mongoose instance and schema
|
|
1906
|
+
*/
|
|
1907
|
+
function createUserModel(mongoose) {
|
|
1908
|
+
return mongoose.models.User || mongoose.model('User', userSchema);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/**
|
|
1912
|
+
* Creates or returns the Token model using the provided mongoose instance and schema
|
|
1913
|
+
*/
|
|
1914
|
+
function createTokenModel(mongoose) {
|
|
1915
|
+
return mongoose.models.Token || mongoose.model('Token', tokenSchema);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
/**
|
|
1919
|
+
* Creates or returns the Session model using the provided mongoose instance and schema
|
|
1920
|
+
*/
|
|
1921
|
+
function createSessionModel(mongoose) {
|
|
1922
|
+
return mongoose.models.Session || mongoose.model('Session', sessionSchema);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* Creates or returns the Balance model using the provided mongoose instance and schema
|
|
1927
|
+
*/
|
|
1928
|
+
function createBalanceModel(mongoose) {
|
|
1929
|
+
return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
const logDir = getLogDirectory();
|
|
1933
|
+
const { NODE_ENV, DEBUG_LOGGING = 'false' } = process.env;
|
|
1934
|
+
const useDebugLogging = (typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true') ||
|
|
1935
|
+
DEBUG_LOGGING === 'true';
|
|
1936
|
+
const levels = {
|
|
1937
|
+
error: 0,
|
|
1938
|
+
warn: 1,
|
|
1939
|
+
info: 2,
|
|
1940
|
+
http: 3,
|
|
1941
|
+
verbose: 4,
|
|
1942
|
+
debug: 5,
|
|
1943
|
+
activity: 6,
|
|
1944
|
+
silly: 7,
|
|
1945
|
+
};
|
|
1946
|
+
winston.addColors({
|
|
1947
|
+
info: 'green',
|
|
1948
|
+
warn: 'italic yellow',
|
|
1949
|
+
error: 'red',
|
|
1950
|
+
debug: 'blue',
|
|
1951
|
+
});
|
|
1952
|
+
const level = () => {
|
|
1953
|
+
const env = NODE_ENV || 'development';
|
|
1954
|
+
const isDevelopment = env === 'development';
|
|
1955
|
+
return isDevelopment ? 'debug' : 'warn';
|
|
1956
|
+
};
|
|
1957
|
+
const fileFormat = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat());
|
|
1958
|
+
const logLevel = useDebugLogging ? 'debug' : 'error';
|
|
1959
|
+
const transports = [
|
|
1960
|
+
new winston.transports.DailyRotateFile({
|
|
1961
|
+
level: logLevel,
|
|
1962
|
+
filename: `${logDir}/meiliSync-%DATE%.log`,
|
|
1963
|
+
datePattern: 'YYYY-MM-DD',
|
|
1964
|
+
zippedArchive: true,
|
|
1965
|
+
maxSize: '20m',
|
|
1966
|
+
maxFiles: '14d',
|
|
1967
|
+
format: fileFormat,
|
|
1968
|
+
}),
|
|
1969
|
+
];
|
|
1970
|
+
const consoleFormat = winston.format.combine(winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`));
|
|
1971
|
+
transports.push(new winston.transports.Console({
|
|
1972
|
+
level: 'info',
|
|
1973
|
+
format: consoleFormat,
|
|
1974
|
+
}));
|
|
1975
|
+
const logger = winston.createLogger({
|
|
1976
|
+
level: level(),
|
|
1977
|
+
levels,
|
|
1978
|
+
transports,
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1380
1981
|
// Environment flags
|
|
1381
1982
|
/**
|
|
1382
1983
|
* Flag to indicate if search is enabled based on environment variables.
|
|
@@ -1472,7 +2073,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1472
2073
|
try {
|
|
1473
2074
|
const startTime = Date.now();
|
|
1474
2075
|
const { batchSize, delayMs } = syncConfig;
|
|
1475
|
-
logger
|
|
2076
|
+
logger.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
|
|
1476
2077
|
// Build query with resume capability
|
|
1477
2078
|
const query = {};
|
|
1478
2079
|
if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
|
|
@@ -1514,7 +2115,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1514
2115
|
updateOps = [];
|
|
1515
2116
|
// Log progress
|
|
1516
2117
|
const progress = Math.round((processedCount / totalCount) * 100);
|
|
1517
|
-
logger
|
|
2118
|
+
logger.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
|
|
1518
2119
|
// Add delay to prevent overwhelming resources
|
|
1519
2120
|
if (delayMs > 0) {
|
|
1520
2121
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
@@ -1526,10 +2127,10 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1526
2127
|
await this.processSyncBatch(index, documentBatch, updateOps);
|
|
1527
2128
|
}
|
|
1528
2129
|
const duration = Date.now() - startTime;
|
|
1529
|
-
logger
|
|
2130
|
+
logger.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
|
|
1530
2131
|
}
|
|
1531
2132
|
catch (error) {
|
|
1532
|
-
logger
|
|
2133
|
+
logger.error('[syncWithMeili] Error during sync:', error);
|
|
1533
2134
|
throw error;
|
|
1534
2135
|
}
|
|
1535
2136
|
}
|
|
@@ -1549,7 +2150,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1549
2150
|
}
|
|
1550
2151
|
}
|
|
1551
2152
|
catch (error) {
|
|
1552
|
-
logger
|
|
2153
|
+
logger.error('[processSyncBatch] Error processing batch:', error);
|
|
1553
2154
|
// Don't throw - allow sync to continue with other documents
|
|
1554
2155
|
}
|
|
1555
2156
|
}
|
|
@@ -1576,7 +2177,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1576
2177
|
const toDelete = meiliIds.filter((id) => !existingIds.has(id));
|
|
1577
2178
|
if (toDelete.length > 0) {
|
|
1578
2179
|
await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
|
|
1579
|
-
logger
|
|
2180
|
+
logger.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
|
|
1580
2181
|
}
|
|
1581
2182
|
offset += batchSize;
|
|
1582
2183
|
// Add delay between batches
|
|
@@ -1586,7 +2187,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1586
2187
|
}
|
|
1587
2188
|
}
|
|
1588
2189
|
catch (error) {
|
|
1589
|
-
logger
|
|
2190
|
+
logger.error('[cleanupMeiliIndex] Error during cleanup:', error);
|
|
1590
2191
|
}
|
|
1591
2192
|
}
|
|
1592
2193
|
/**
|
|
@@ -1656,7 +2257,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1656
2257
|
catch (error) {
|
|
1657
2258
|
retryCount++;
|
|
1658
2259
|
if (retryCount >= maxRetries) {
|
|
1659
|
-
logger
|
|
2260
|
+
logger.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
|
|
1660
2261
|
return next();
|
|
1661
2262
|
}
|
|
1662
2263
|
// Exponential backoff
|
|
@@ -1667,7 +2268,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1667
2268
|
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
|
|
1668
2269
|
}
|
|
1669
2270
|
catch (error) {
|
|
1670
|
-
logger
|
|
2271
|
+
logger.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
|
|
1671
2272
|
return next();
|
|
1672
2273
|
}
|
|
1673
2274
|
next();
|
|
@@ -1682,7 +2283,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1682
2283
|
next();
|
|
1683
2284
|
}
|
|
1684
2285
|
catch (error) {
|
|
1685
|
-
logger
|
|
2286
|
+
logger.error('[updateObjectToMeili] Error updating document in Meili:', error);
|
|
1686
2287
|
return next();
|
|
1687
2288
|
}
|
|
1688
2289
|
}
|
|
@@ -1697,7 +2298,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
|
|
|
1697
2298
|
next();
|
|
1698
2299
|
}
|
|
1699
2300
|
catch (error) {
|
|
1700
|
-
logger
|
|
2301
|
+
logger.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
|
|
1701
2302
|
return next();
|
|
1702
2303
|
}
|
|
1703
2304
|
}
|
|
@@ -1789,23 +2390,23 @@ function mongoMeili(schema, options) {
|
|
|
1789
2390
|
(async () => {
|
|
1790
2391
|
try {
|
|
1791
2392
|
await index.getRawInfo();
|
|
1792
|
-
logger
|
|
2393
|
+
logger.debug(`[mongoMeili] Index ${indexName} already exists`);
|
|
1793
2394
|
}
|
|
1794
2395
|
catch (error) {
|
|
1795
2396
|
const errorCode = error === null || error === void 0 ? void 0 : error.code;
|
|
1796
2397
|
if (errorCode === 'index_not_found') {
|
|
1797
2398
|
try {
|
|
1798
|
-
logger
|
|
2399
|
+
logger.info(`[mongoMeili] Creating new index: ${indexName}`);
|
|
1799
2400
|
await client.createIndex(indexName, { primaryKey });
|
|
1800
|
-
logger
|
|
2401
|
+
logger.info(`[mongoMeili] Successfully created index: ${indexName}`);
|
|
1801
2402
|
}
|
|
1802
2403
|
catch (createError) {
|
|
1803
2404
|
// Index might have been created by another instance
|
|
1804
|
-
logger
|
|
2405
|
+
logger.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
|
|
1805
2406
|
}
|
|
1806
2407
|
}
|
|
1807
2408
|
else {
|
|
1808
|
-
logger
|
|
2409
|
+
logger.error(`[mongoMeili] Error checking index ${indexName}:`, error);
|
|
1809
2410
|
}
|
|
1810
2411
|
}
|
|
1811
2412
|
})();
|
|
@@ -1868,7 +2469,7 @@ function mongoMeili(schema, options) {
|
|
|
1868
2469
|
}
|
|
1869
2470
|
catch (error) {
|
|
1870
2471
|
if (meiliEnabled) {
|
|
1871
|
-
logger
|
|
2472
|
+
logger.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
|
|
1872
2473
|
}
|
|
1873
2474
|
return next();
|
|
1874
2475
|
}
|
|
@@ -1888,7 +2489,7 @@ function mongoMeili(schema, options) {
|
|
|
1888
2489
|
meiliDoc = await client.index('convos').getDocument(doc.conversationId);
|
|
1889
2490
|
}
|
|
1890
2491
|
catch (error) {
|
|
1891
|
-
logger
|
|
2492
|
+
logger.debug('[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' +
|
|
1892
2493
|
doc.conversationId, error);
|
|
1893
2494
|
}
|
|
1894
2495
|
}
|
|
@@ -1939,6 +2540,13 @@ function createAgentModel(mongoose) {
|
|
|
1939
2540
|
return mongoose.models.Agent || mongoose.model('Agent', agentSchema);
|
|
1940
2541
|
}
|
|
1941
2542
|
|
|
2543
|
+
/**
|
|
2544
|
+
* Creates or returns the AgentCategory model using the provided mongoose instance and schema
|
|
2545
|
+
*/
|
|
2546
|
+
function createAgentCategoryModel(mongoose) {
|
|
2547
|
+
return mongoose.models.AgentCategory || mongoose.model('AgentCategory', agentCategorySchema);
|
|
2548
|
+
}
|
|
2549
|
+
|
|
1942
2550
|
/**
|
|
1943
2551
|
* Creates or returns the Role model using the provided mongoose instance and schema
|
|
1944
2552
|
*/
|
|
@@ -2050,6 +2658,108 @@ function createMemoryModel(mongoose) {
|
|
|
2050
2658
|
return mongoose.models.MemoryEntry || mongoose.model('MemoryEntry', MemoryEntrySchema);
|
|
2051
2659
|
}
|
|
2052
2660
|
|
|
2661
|
+
const accessRoleSchema = new Schema({
|
|
2662
|
+
accessRoleId: {
|
|
2663
|
+
type: String,
|
|
2664
|
+
required: true,
|
|
2665
|
+
index: true,
|
|
2666
|
+
unique: true,
|
|
2667
|
+
},
|
|
2668
|
+
name: {
|
|
2669
|
+
type: String,
|
|
2670
|
+
required: true,
|
|
2671
|
+
},
|
|
2672
|
+
description: String,
|
|
2673
|
+
resourceType: {
|
|
2674
|
+
type: String,
|
|
2675
|
+
enum: ['agent', 'project', 'file', 'promptGroup'],
|
|
2676
|
+
required: true,
|
|
2677
|
+
default: 'agent',
|
|
2678
|
+
},
|
|
2679
|
+
permBits: {
|
|
2680
|
+
type: Number,
|
|
2681
|
+
required: true,
|
|
2682
|
+
},
|
|
2683
|
+
}, { timestamps: true });
|
|
2684
|
+
|
|
2685
|
+
/**
|
|
2686
|
+
* Creates or returns the AccessRole model using the provided mongoose instance and schema
|
|
2687
|
+
*/
|
|
2688
|
+
function createAccessRoleModel(mongoose) {
|
|
2689
|
+
return (mongoose.models.AccessRole || mongoose.model('AccessRole', accessRoleSchema));
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
const aclEntrySchema = new Schema({
|
|
2693
|
+
principalType: {
|
|
2694
|
+
type: String,
|
|
2695
|
+
enum: Object.values(PrincipalType),
|
|
2696
|
+
required: true,
|
|
2697
|
+
},
|
|
2698
|
+
principalId: {
|
|
2699
|
+
type: Schema.Types.Mixed, // Can be ObjectId for users/groups or String for roles
|
|
2700
|
+
refPath: 'principalModel',
|
|
2701
|
+
required: function () {
|
|
2702
|
+
return this.principalType !== PrincipalType.PUBLIC;
|
|
2703
|
+
},
|
|
2704
|
+
index: true,
|
|
2705
|
+
},
|
|
2706
|
+
principalModel: {
|
|
2707
|
+
type: String,
|
|
2708
|
+
enum: Object.values(PrincipalModel),
|
|
2709
|
+
required: function () {
|
|
2710
|
+
return this.principalType !== PrincipalType.PUBLIC;
|
|
2711
|
+
},
|
|
2712
|
+
},
|
|
2713
|
+
resourceType: {
|
|
2714
|
+
type: String,
|
|
2715
|
+
enum: Object.values(ResourceType),
|
|
2716
|
+
required: true,
|
|
2717
|
+
},
|
|
2718
|
+
resourceId: {
|
|
2719
|
+
type: Schema.Types.ObjectId,
|
|
2720
|
+
required: true,
|
|
2721
|
+
index: true,
|
|
2722
|
+
},
|
|
2723
|
+
permBits: {
|
|
2724
|
+
type: Number,
|
|
2725
|
+
default: 1,
|
|
2726
|
+
},
|
|
2727
|
+
roleId: {
|
|
2728
|
+
type: Schema.Types.ObjectId,
|
|
2729
|
+
ref: 'AccessRole',
|
|
2730
|
+
},
|
|
2731
|
+
inheritedFrom: {
|
|
2732
|
+
type: Schema.Types.ObjectId,
|
|
2733
|
+
sparse: true,
|
|
2734
|
+
index: true,
|
|
2735
|
+
},
|
|
2736
|
+
grantedBy: {
|
|
2737
|
+
type: Schema.Types.ObjectId,
|
|
2738
|
+
ref: 'User',
|
|
2739
|
+
},
|
|
2740
|
+
grantedAt: {
|
|
2741
|
+
type: Date,
|
|
2742
|
+
default: Date.now,
|
|
2743
|
+
},
|
|
2744
|
+
}, { timestamps: true });
|
|
2745
|
+
aclEntrySchema.index({ principalId: 1, principalType: 1, resourceType: 1, resourceId: 1 });
|
|
2746
|
+
aclEntrySchema.index({ resourceId: 1, principalType: 1, principalId: 1 });
|
|
2747
|
+
aclEntrySchema.index({ principalId: 1, permBits: 1, resourceType: 1 });
|
|
2748
|
+
|
|
2749
|
+
/**
|
|
2750
|
+
* Creates or returns the AclEntry model using the provided mongoose instance and schema
|
|
2751
|
+
*/
|
|
2752
|
+
function createAclEntryModel(mongoose) {
|
|
2753
|
+
return mongoose.models.AclEntry || mongoose.model('AclEntry', aclEntrySchema);
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
/**
|
|
2757
|
+
* Creates or returns the Group model using the provided mongoose instance and schema
|
|
2758
|
+
*/
|
|
2759
|
+
function createGroupModel(mongoose) {
|
|
2760
|
+
return mongoose.models.Group || mongoose.model('Group', groupSchema);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2053
2763
|
/**
|
|
2054
2764
|
* Creates all database models for all collections
|
|
2055
2765
|
*/
|
|
@@ -2062,6 +2772,7 @@ function createModels(mongoose) {
|
|
|
2062
2772
|
Conversation: createConversationModel(mongoose),
|
|
2063
2773
|
Message: createMessageModel(mongoose),
|
|
2064
2774
|
Agent: createAgentModel(mongoose),
|
|
2775
|
+
AgentCategory: createAgentCategoryModel(mongoose),
|
|
2065
2776
|
Role: createRoleModel(mongoose),
|
|
2066
2777
|
Action: createActionModel(mongoose),
|
|
2067
2778
|
Assistant: createAssistantModel(mongoose),
|
|
@@ -2078,935 +2789,1951 @@ function createModels(mongoose) {
|
|
|
2078
2789
|
SharedLink: createSharedLinkModel(mongoose),
|
|
2079
2790
|
ToolCall: createToolCallModel(mongoose),
|
|
2080
2791
|
MemoryEntry: createMemoryModel(mongoose),
|
|
2792
|
+
AccessRole: createAccessRoleModel(mongoose),
|
|
2793
|
+
AclEntry: createAclEntryModel(mongoose),
|
|
2794
|
+
Group: createGroupModel(mongoose),
|
|
2081
2795
|
};
|
|
2082
2796
|
}
|
|
2083
2797
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
const User = mongoose.models.User;
|
|
2091
|
-
const query = User.findOne(searchCriteria);
|
|
2092
|
-
if (fieldsToSelect) {
|
|
2093
|
-
query.select(fieldsToSelect);
|
|
2094
|
-
}
|
|
2095
|
-
return (await query.lean());
|
|
2096
|
-
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Count the number of user documents in the collection based on the provided filter.
|
|
2099
|
-
*/
|
|
2100
|
-
async function countUsers(filter = {}) {
|
|
2101
|
-
const User = mongoose.models.User;
|
|
2102
|
-
return await User.countDocuments(filter);
|
|
2798
|
+
var _a;
|
|
2799
|
+
class SessionError extends Error {
|
|
2800
|
+
constructor(message, code = 'SESSION_ERROR') {
|
|
2801
|
+
super(message);
|
|
2802
|
+
this.name = 'SessionError';
|
|
2803
|
+
this.code = code;
|
|
2103
2804
|
}
|
|
2805
|
+
}
|
|
2806
|
+
const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
|
|
2807
|
+
const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
2808
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
2809
|
+
function createSessionMethods(mongoose) {
|
|
2104
2810
|
/**
|
|
2105
|
-
* Creates a new
|
|
2811
|
+
* Creates a new session for a user
|
|
2106
2812
|
*/
|
|
2107
|
-
async function
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
const userData = {
|
|
2111
|
-
...data,
|
|
2112
|
-
expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
|
2113
|
-
};
|
|
2114
|
-
if (disableTTL) {
|
|
2115
|
-
delete userData.expiresAt;
|
|
2813
|
+
async function createSession(userId, options = {}) {
|
|
2814
|
+
if (!userId) {
|
|
2815
|
+
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2116
2816
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
};
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
balanceConfig.refillIntervalUnit != null &&
|
|
2126
|
-
balanceConfig.refillAmount != null) {
|
|
2127
|
-
update.$set = {
|
|
2128
|
-
autoRefillEnabled: true,
|
|
2129
|
-
refillIntervalValue: balanceConfig.refillIntervalValue,
|
|
2130
|
-
refillIntervalUnit: balanceConfig.refillIntervalUnit,
|
|
2131
|
-
refillAmount: balanceConfig.refillAmount,
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
await Balance.findOneAndUpdate({ user: user._id }, update, {
|
|
2135
|
-
upsert: true,
|
|
2136
|
-
new: true,
|
|
2137
|
-
}).lean();
|
|
2817
|
+
try {
|
|
2818
|
+
const Session = mongoose.models.Session;
|
|
2819
|
+
const currentSession = new Session({
|
|
2820
|
+
user: userId,
|
|
2821
|
+
expiration: options.expiration || new Date(Date.now() + expires),
|
|
2822
|
+
});
|
|
2823
|
+
const refreshToken = await generateRefreshToken(currentSession);
|
|
2824
|
+
return { session: currentSession, refreshToken };
|
|
2138
2825
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2826
|
+
catch (error) {
|
|
2827
|
+
logger$1.error('[createSession] Error creating session:', error);
|
|
2828
|
+
throw new SessionError('Failed to create session', 'CREATE_SESSION_FAILED');
|
|
2141
2829
|
}
|
|
2142
|
-
return user._id;
|
|
2143
2830
|
}
|
|
2144
2831
|
/**
|
|
2145
|
-
*
|
|
2832
|
+
* Finds a session by various parameters
|
|
2146
2833
|
*/
|
|
2147
|
-
async function
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2834
|
+
async function findSession(params, options = { lean: true }) {
|
|
2835
|
+
try {
|
|
2836
|
+
const Session = mongoose.models.Session;
|
|
2837
|
+
const query = {};
|
|
2838
|
+
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
|
2839
|
+
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
|
2840
|
+
}
|
|
2841
|
+
if (params.refreshToken) {
|
|
2842
|
+
const tokenHash = await hashToken(params.refreshToken);
|
|
2843
|
+
query.refreshTokenHash = tokenHash;
|
|
2844
|
+
}
|
|
2845
|
+
if (params.userId) {
|
|
2846
|
+
query.user = params.userId;
|
|
2847
|
+
}
|
|
2848
|
+
if (params.sessionId) {
|
|
2849
|
+
const sessionId = typeof params.sessionId === 'object' &&
|
|
2850
|
+
params.sessionId !== null &&
|
|
2851
|
+
'sessionId' in params.sessionId
|
|
2852
|
+
? params.sessionId.sessionId
|
|
2853
|
+
: params.sessionId;
|
|
2854
|
+
if (!mongoose.Types.ObjectId.isValid(sessionId)) {
|
|
2855
|
+
throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
|
|
2856
|
+
}
|
|
2857
|
+
query._id = sessionId;
|
|
2858
|
+
}
|
|
2859
|
+
// Add expiration check to only return valid sessions
|
|
2860
|
+
query.expiration = { $gt: new Date() };
|
|
2861
|
+
const sessionQuery = Session.findOne(query);
|
|
2862
|
+
if (options.lean) {
|
|
2863
|
+
return (await sessionQuery.lean());
|
|
2864
|
+
}
|
|
2865
|
+
return await sessionQuery.exec();
|
|
2866
|
+
}
|
|
2867
|
+
catch (error) {
|
|
2868
|
+
logger$1.error('[findSession] Error finding session:', error);
|
|
2869
|
+
throw new SessionError('Failed to find session', 'FIND_SESSION_FAILED');
|
|
2870
|
+
}
|
|
2157
2871
|
}
|
|
2158
2872
|
/**
|
|
2159
|
-
*
|
|
2873
|
+
* Updates session expiration
|
|
2160
2874
|
*/
|
|
2161
|
-
async function
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2875
|
+
async function updateExpiration(session, newExpiration) {
|
|
2876
|
+
try {
|
|
2877
|
+
const Session = mongoose.models.Session;
|
|
2878
|
+
const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
|
|
2879
|
+
if (!sessionDoc) {
|
|
2880
|
+
throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
|
|
2881
|
+
}
|
|
2882
|
+
sessionDoc.expiration = newExpiration || new Date(Date.now() + expires);
|
|
2883
|
+
return await sessionDoc.save();
|
|
2884
|
+
}
|
|
2885
|
+
catch (error) {
|
|
2886
|
+
logger$1.error('[updateExpiration] Error updating session:', error);
|
|
2887
|
+
throw new SessionError('Failed to update session expiration', 'UPDATE_EXPIRATION_FAILED');
|
|
2166
2888
|
}
|
|
2167
|
-
return (await query.lean());
|
|
2168
2889
|
}
|
|
2169
2890
|
/**
|
|
2170
|
-
*
|
|
2891
|
+
* Deletes a session by refresh token or session ID
|
|
2171
2892
|
*/
|
|
2172
|
-
async function
|
|
2893
|
+
async function deleteSession(params) {
|
|
2173
2894
|
try {
|
|
2174
|
-
const
|
|
2175
|
-
|
|
2895
|
+
const Session = mongoose.models.Session;
|
|
2896
|
+
if (!params.refreshToken && !params.sessionId) {
|
|
2897
|
+
throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
|
|
2898
|
+
}
|
|
2899
|
+
const query = {};
|
|
2900
|
+
if (params.refreshToken) {
|
|
2901
|
+
query.refreshTokenHash = await hashToken(params.refreshToken);
|
|
2902
|
+
}
|
|
2903
|
+
if (params.sessionId) {
|
|
2904
|
+
query._id = params.sessionId;
|
|
2905
|
+
}
|
|
2906
|
+
const result = await Session.deleteOne(query);
|
|
2176
2907
|
if (result.deletedCount === 0) {
|
|
2177
|
-
|
|
2908
|
+
logger$1.warn('[deleteSession] No session found to delete');
|
|
2178
2909
|
}
|
|
2179
|
-
return
|
|
2910
|
+
return result;
|
|
2180
2911
|
}
|
|
2181
2912
|
catch (error) {
|
|
2182
|
-
|
|
2183
|
-
throw new
|
|
2913
|
+
logger$1.error('[deleteSession] Error deleting session:', error);
|
|
2914
|
+
throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
|
|
2184
2915
|
}
|
|
2185
2916
|
}
|
|
2186
2917
|
/**
|
|
2187
|
-
*
|
|
2918
|
+
* Deletes all sessions for a user
|
|
2188
2919
|
*/
|
|
2189
|
-
async function
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (process.env.SESSION_EXPIRY !== undefined && process.env.SESSION_EXPIRY !== '') {
|
|
2195
|
-
try {
|
|
2196
|
-
const evaluated = eval(process.env.SESSION_EXPIRY);
|
|
2197
|
-
if (evaluated) {
|
|
2198
|
-
expires = evaluated;
|
|
2199
|
-
}
|
|
2920
|
+
async function deleteAllUserSessions(userId, options = {}) {
|
|
2921
|
+
try {
|
|
2922
|
+
const Session = mongoose.models.Session;
|
|
2923
|
+
if (!userId) {
|
|
2924
|
+
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2200
2925
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2926
|
+
const userIdString = typeof userId === 'object' && userId !== null ? userId.userId : userId;
|
|
2927
|
+
if (!mongoose.Types.ObjectId.isValid(userIdString)) {
|
|
2928
|
+
throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
|
|
2203
2929
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2930
|
+
const query = { user: userIdString };
|
|
2931
|
+
if (options.excludeCurrentSession && options.currentSessionId) {
|
|
2932
|
+
query._id = { $ne: options.currentSessionId };
|
|
2933
|
+
}
|
|
2934
|
+
const result = await Session.deleteMany(query);
|
|
2935
|
+
if (result.deletedCount && result.deletedCount > 0) {
|
|
2936
|
+
logger$1.debug(`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`);
|
|
2937
|
+
}
|
|
2938
|
+
return result;
|
|
2939
|
+
}
|
|
2940
|
+
catch (error) {
|
|
2941
|
+
logger$1.error('[deleteAllUserSessions] Error deleting user sessions:', error);
|
|
2942
|
+
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Generates a refresh token for a session
|
|
2947
|
+
*/
|
|
2948
|
+
async function generateRefreshToken(session) {
|
|
2949
|
+
if (!session || !session.user) {
|
|
2950
|
+
throw new SessionError('Invalid session object', 'INVALID_SESSION');
|
|
2951
|
+
}
|
|
2952
|
+
try {
|
|
2953
|
+
const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
|
|
2954
|
+
if (!session.expiration) {
|
|
2955
|
+
session.expiration = new Date(expiresIn);
|
|
2956
|
+
}
|
|
2957
|
+
const refreshToken = await signPayload({
|
|
2958
|
+
payload: {
|
|
2959
|
+
id: session.user,
|
|
2960
|
+
sessionId: session._id,
|
|
2961
|
+
},
|
|
2962
|
+
secret: process.env.JWT_REFRESH_SECRET,
|
|
2963
|
+
expirationTime: Math.floor((expiresIn - Date.now()) / 1000),
|
|
2964
|
+
});
|
|
2965
|
+
session.refreshTokenHash = await hashToken(refreshToken);
|
|
2966
|
+
await session.save();
|
|
2967
|
+
return refreshToken;
|
|
2968
|
+
}
|
|
2969
|
+
catch (error) {
|
|
2970
|
+
logger$1.error('[generateRefreshToken] Error generating refresh token:', error);
|
|
2971
|
+
throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
/**
|
|
2975
|
+
* Counts active sessions for a user
|
|
2976
|
+
*/
|
|
2977
|
+
async function countActiveSessions(userId) {
|
|
2978
|
+
try {
|
|
2979
|
+
const Session = mongoose.models.Session;
|
|
2980
|
+
if (!userId) {
|
|
2981
|
+
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2982
|
+
}
|
|
2983
|
+
return await Session.countDocuments({
|
|
2984
|
+
user: userId,
|
|
2985
|
+
expiration: { $gt: new Date() },
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
catch (error) {
|
|
2989
|
+
logger$1.error('[countActiveSessions] Error counting active sessions:', error);
|
|
2990
|
+
throw new SessionError('Failed to count active sessions', 'COUNT_SESSIONS_FAILED');
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
return {
|
|
2994
|
+
findSession,
|
|
2995
|
+
SessionError,
|
|
2996
|
+
deleteSession,
|
|
2997
|
+
createSession,
|
|
2998
|
+
updateExpiration,
|
|
2999
|
+
countActiveSessions,
|
|
3000
|
+
generateRefreshToken,
|
|
3001
|
+
deleteAllUserSessions,
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
3006
|
+
function createTokenMethods(mongoose) {
|
|
3007
|
+
/**
|
|
3008
|
+
* Creates a new Token instance.
|
|
3009
|
+
*/
|
|
3010
|
+
async function createToken(tokenData) {
|
|
3011
|
+
try {
|
|
3012
|
+
const Token = mongoose.models.Token;
|
|
3013
|
+
const currentTime = new Date();
|
|
3014
|
+
const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
|
|
3015
|
+
const newTokenData = {
|
|
3016
|
+
...tokenData,
|
|
3017
|
+
createdAt: currentTime,
|
|
3018
|
+
expiresAt,
|
|
3019
|
+
};
|
|
3020
|
+
return await Token.create(newTokenData);
|
|
3021
|
+
}
|
|
3022
|
+
catch (error) {
|
|
3023
|
+
logger$1.debug('An error occurred while creating token:', error);
|
|
3024
|
+
throw error;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* Updates a Token document that matches the provided query.
|
|
3029
|
+
*/
|
|
3030
|
+
async function updateToken(query, updateData) {
|
|
3031
|
+
try {
|
|
3032
|
+
const Token = mongoose.models.Token;
|
|
3033
|
+
return await Token.findOneAndUpdate(query, updateData, { new: true });
|
|
3034
|
+
}
|
|
3035
|
+
catch (error) {
|
|
3036
|
+
logger$1.debug('An error occurred while updating token:', error);
|
|
3037
|
+
throw error;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Deletes all Token documents that match the provided token, user ID, or email.
|
|
3042
|
+
*/
|
|
3043
|
+
async function deleteTokens(query) {
|
|
3044
|
+
try {
|
|
3045
|
+
const Token = mongoose.models.Token;
|
|
3046
|
+
return await Token.deleteMany({
|
|
3047
|
+
$or: [
|
|
3048
|
+
{ userId: query.userId },
|
|
3049
|
+
{ token: query.token },
|
|
3050
|
+
{ email: query.email },
|
|
3051
|
+
{ identifier: query.identifier },
|
|
3052
|
+
],
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
catch (error) {
|
|
3056
|
+
logger$1.debug('An error occurred while deleting tokens:', error);
|
|
3057
|
+
throw error;
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
/**
|
|
3061
|
+
* Finds a Token document that matches the provided query.
|
|
3062
|
+
*/
|
|
3063
|
+
async function findToken(query) {
|
|
3064
|
+
try {
|
|
3065
|
+
const Token = mongoose.models.Token;
|
|
3066
|
+
const conditions = [];
|
|
3067
|
+
if (query.userId) {
|
|
3068
|
+
conditions.push({ userId: query.userId });
|
|
3069
|
+
}
|
|
3070
|
+
if (query.token) {
|
|
3071
|
+
conditions.push({ token: query.token });
|
|
3072
|
+
}
|
|
3073
|
+
if (query.email) {
|
|
3074
|
+
conditions.push({ email: query.email });
|
|
3075
|
+
}
|
|
3076
|
+
if (query.identifier) {
|
|
3077
|
+
conditions.push({ identifier: query.identifier });
|
|
3078
|
+
}
|
|
3079
|
+
const token = await Token.findOne({
|
|
3080
|
+
$and: conditions,
|
|
3081
|
+
}).lean();
|
|
3082
|
+
return token;
|
|
3083
|
+
}
|
|
3084
|
+
catch (error) {
|
|
3085
|
+
logger$1.debug('An error occurred while finding token:', error);
|
|
3086
|
+
throw error;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
// Return all methods
|
|
3090
|
+
return {
|
|
3091
|
+
findToken,
|
|
3092
|
+
createToken,
|
|
3093
|
+
updateToken,
|
|
3094
|
+
deleteTokens,
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
3099
|
+
function createRoleMethods(mongoose) {
|
|
3100
|
+
/**
|
|
3101
|
+
* Initialize default roles in the system.
|
|
3102
|
+
* Creates the default roles (ADMIN, USER) if they don't exist in the database.
|
|
3103
|
+
* Updates existing roles with new permission types if they're missing.
|
|
3104
|
+
*/
|
|
3105
|
+
async function initializeRoles() {
|
|
3106
|
+
var _a, _b;
|
|
3107
|
+
const Role = mongoose.models.Role;
|
|
3108
|
+
for (const roleName of [SystemRoles.ADMIN, SystemRoles.USER]) {
|
|
3109
|
+
let role = await Role.findOne({ name: roleName });
|
|
3110
|
+
const defaultPerms = roleDefaults[roleName].permissions;
|
|
3111
|
+
if (!role) {
|
|
3112
|
+
role = new Role(roleDefaults[roleName]);
|
|
3113
|
+
}
|
|
3114
|
+
else {
|
|
3115
|
+
const permissions = (_b = (_a = role.toObject()) === null || _a === void 0 ? void 0 : _a.permissions) !== null && _b !== void 0 ? _b : {};
|
|
3116
|
+
role.permissions = role.permissions || {};
|
|
3117
|
+
for (const permType of Object.keys(defaultPerms)) {
|
|
3118
|
+
if (permissions[permType] == null || Object.keys(permissions[permType]).length === 0) {
|
|
3119
|
+
role.permissions[permType] = defaultPerms[permType];
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
await role.save();
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* List all roles in the system (for testing purposes)
|
|
3128
|
+
* Returns an array of all roles with their names and permissions
|
|
3129
|
+
*/
|
|
3130
|
+
async function listRoles() {
|
|
3131
|
+
const Role = mongoose.models.Role;
|
|
3132
|
+
return await Role.find({}).select('name permissions').lean();
|
|
3133
|
+
}
|
|
3134
|
+
// Return all methods you want to expose
|
|
3135
|
+
return {
|
|
3136
|
+
listRoles,
|
|
3137
|
+
initializeRoles,
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
/** Factory function that takes mongoose instance and returns the methods */
|
|
3142
|
+
function createUserMethods(mongoose) {
|
|
3143
|
+
/**
|
|
3144
|
+
* Search for a single user based on partial data and return matching user document as plain object.
|
|
3145
|
+
*/
|
|
3146
|
+
async function findUser(searchCriteria, fieldsToSelect) {
|
|
3147
|
+
const User = mongoose.models.User;
|
|
3148
|
+
const query = User.findOne(searchCriteria);
|
|
3149
|
+
if (fieldsToSelect) {
|
|
3150
|
+
query.select(fieldsToSelect);
|
|
3151
|
+
}
|
|
3152
|
+
return (await query.lean());
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Count the number of user documents in the collection based on the provided filter.
|
|
3156
|
+
*/
|
|
3157
|
+
async function countUsers(filter = {}) {
|
|
3158
|
+
const User = mongoose.models.User;
|
|
3159
|
+
return await User.countDocuments(filter);
|
|
3160
|
+
}
|
|
3161
|
+
/**
|
|
3162
|
+
* Creates a new user, optionally with a TTL of 1 week.
|
|
3163
|
+
*/
|
|
3164
|
+
async function createUser(data, balanceConfig, disableTTL = true, returnUser = false) {
|
|
3165
|
+
const User = mongoose.models.User;
|
|
3166
|
+
const Balance = mongoose.models.Balance;
|
|
3167
|
+
const userData = {
|
|
3168
|
+
...data,
|
|
3169
|
+
expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
|
3170
|
+
};
|
|
3171
|
+
if (disableTTL) {
|
|
3172
|
+
delete userData.expiresAt;
|
|
3173
|
+
}
|
|
3174
|
+
const user = await User.create(userData);
|
|
3175
|
+
// If balance is enabled, create or update a balance record for the user
|
|
3176
|
+
if ((balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.enabled) && (balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.startBalance)) {
|
|
3177
|
+
const update = {
|
|
3178
|
+
$inc: { tokenCredits: balanceConfig.startBalance },
|
|
3179
|
+
};
|
|
3180
|
+
if (balanceConfig.autoRefillEnabled &&
|
|
3181
|
+
balanceConfig.refillIntervalValue != null &&
|
|
3182
|
+
balanceConfig.refillIntervalUnit != null &&
|
|
3183
|
+
balanceConfig.refillAmount != null) {
|
|
3184
|
+
update.$set = {
|
|
3185
|
+
autoRefillEnabled: true,
|
|
3186
|
+
refillIntervalValue: balanceConfig.refillIntervalValue,
|
|
3187
|
+
refillIntervalUnit: balanceConfig.refillIntervalUnit,
|
|
3188
|
+
refillAmount: balanceConfig.refillAmount,
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
await Balance.findOneAndUpdate({ user: user._id }, update, {
|
|
3192
|
+
upsert: true,
|
|
3193
|
+
new: true,
|
|
3194
|
+
}).lean();
|
|
3195
|
+
}
|
|
3196
|
+
if (returnUser) {
|
|
3197
|
+
return user.toObject();
|
|
3198
|
+
}
|
|
3199
|
+
return user._id;
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Update a user with new data without overwriting existing properties.
|
|
3203
|
+
*/
|
|
3204
|
+
async function updateUser(userId, updateData) {
|
|
3205
|
+
const User = mongoose.models.User;
|
|
3206
|
+
const updateOperation = {
|
|
3207
|
+
$set: updateData,
|
|
3208
|
+
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
|
3209
|
+
};
|
|
3210
|
+
return (await User.findByIdAndUpdate(userId, updateOperation, {
|
|
3211
|
+
new: true,
|
|
3212
|
+
runValidators: true,
|
|
3213
|
+
}).lean());
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* Retrieve a user by ID and convert the found user document to a plain object.
|
|
3217
|
+
*/
|
|
3218
|
+
async function getUserById(userId, fieldsToSelect) {
|
|
3219
|
+
const User = mongoose.models.User;
|
|
3220
|
+
const query = User.findById(userId);
|
|
3221
|
+
if (fieldsToSelect) {
|
|
3222
|
+
query.select(fieldsToSelect);
|
|
3223
|
+
}
|
|
3224
|
+
return (await query.lean());
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Delete a user by their unique ID.
|
|
3228
|
+
*/
|
|
3229
|
+
async function deleteUserById(userId) {
|
|
3230
|
+
try {
|
|
3231
|
+
const User = mongoose.models.User;
|
|
3232
|
+
const result = await User.deleteOne({ _id: userId });
|
|
3233
|
+
if (result.deletedCount === 0) {
|
|
3234
|
+
return { deletedCount: 0, message: 'No user found with that ID.' };
|
|
3235
|
+
}
|
|
3236
|
+
return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
|
|
3237
|
+
}
|
|
3238
|
+
catch (error) {
|
|
3239
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
3240
|
+
throw new Error('Error deleting user: ' + errorMessage);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* Generates a JWT token for a given user.
|
|
3245
|
+
*/
|
|
3246
|
+
async function generateToken(user) {
|
|
3247
|
+
if (!user) {
|
|
3248
|
+
throw new Error('No user provided');
|
|
3249
|
+
}
|
|
3250
|
+
let expires = 1000 * 60 * 15;
|
|
3251
|
+
if (process.env.SESSION_EXPIRY !== undefined && process.env.SESSION_EXPIRY !== '') {
|
|
3252
|
+
try {
|
|
3253
|
+
const evaluated = eval(process.env.SESSION_EXPIRY);
|
|
3254
|
+
if (evaluated) {
|
|
3255
|
+
expires = evaluated;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
catch (error) {
|
|
3259
|
+
console.warn('Invalid SESSION_EXPIRY expression, using default:', error);
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
return await signPayload({
|
|
2206
3263
|
payload: {
|
|
2207
3264
|
id: user._id,
|
|
2208
3265
|
username: user.username,
|
|
2209
3266
|
provider: user.provider,
|
|
2210
3267
|
email: user.email,
|
|
2211
3268
|
},
|
|
2212
|
-
secret: process.env.JWT_SECRET,
|
|
2213
|
-
expirationTime: expires / 1000,
|
|
2214
|
-
});
|
|
3269
|
+
secret: process.env.JWT_SECRET,
|
|
3270
|
+
expirationTime: expires / 1000,
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Update a user's personalization memories setting.
|
|
3275
|
+
* Handles the edge case where the personalization object doesn't exist.
|
|
3276
|
+
*/
|
|
3277
|
+
async function toggleUserMemories(userId, memoriesEnabled) {
|
|
3278
|
+
const User = mongoose.models.User;
|
|
3279
|
+
// First, ensure the personalization object exists
|
|
3280
|
+
const user = await User.findById(userId);
|
|
3281
|
+
if (!user) {
|
|
3282
|
+
return null;
|
|
3283
|
+
}
|
|
3284
|
+
// Use $set to update the nested field, which will create the personalization object if it doesn't exist
|
|
3285
|
+
const updateOperation = {
|
|
3286
|
+
$set: {
|
|
3287
|
+
'personalization.memories': memoriesEnabled,
|
|
3288
|
+
},
|
|
3289
|
+
};
|
|
3290
|
+
return (await User.findByIdAndUpdate(userId, updateOperation, {
|
|
3291
|
+
new: true,
|
|
3292
|
+
runValidators: true,
|
|
3293
|
+
}).lean());
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Search for users by pattern matching on name, email, or username (case-insensitive)
|
|
3297
|
+
* @param searchPattern - The pattern to search for
|
|
3298
|
+
* @param limit - Maximum number of results to return
|
|
3299
|
+
* @param fieldsToSelect - The fields to include or exclude in the returned documents
|
|
3300
|
+
* @returns Array of matching user documents
|
|
3301
|
+
*/
|
|
3302
|
+
const searchUsers = async function ({ searchPattern, limit = 20, fieldsToSelect = null, }) {
|
|
3303
|
+
if (!searchPattern || searchPattern.trim().length === 0) {
|
|
3304
|
+
return [];
|
|
3305
|
+
}
|
|
3306
|
+
const regex = new RegExp(searchPattern.trim(), 'i');
|
|
3307
|
+
const User = mongoose.models.User;
|
|
3308
|
+
const query = User.find({
|
|
3309
|
+
$or: [{ email: regex }, { name: regex }, { username: regex }],
|
|
3310
|
+
}).limit(limit * 2); // Get more results to allow for relevance sorting
|
|
3311
|
+
if (fieldsToSelect) {
|
|
3312
|
+
query.select(fieldsToSelect);
|
|
3313
|
+
}
|
|
3314
|
+
const users = await query.lean();
|
|
3315
|
+
// Score results by relevance
|
|
3316
|
+
const exactRegex = new RegExp(`^${searchPattern.trim()}$`, 'i');
|
|
3317
|
+
const startsWithPattern = searchPattern.trim().toLowerCase();
|
|
3318
|
+
const scoredUsers = users.map((user) => {
|
|
3319
|
+
const searchableFields = [user.name, user.email, user.username].filter(Boolean);
|
|
3320
|
+
let maxScore = 0;
|
|
3321
|
+
for (const field of searchableFields) {
|
|
3322
|
+
const fieldLower = field.toLowerCase();
|
|
3323
|
+
let score = 0;
|
|
3324
|
+
// Exact match gets highest score
|
|
3325
|
+
if (exactRegex.test(field)) {
|
|
3326
|
+
score = 100;
|
|
3327
|
+
}
|
|
3328
|
+
// Starts with query gets high score
|
|
3329
|
+
else if (fieldLower.startsWith(startsWithPattern)) {
|
|
3330
|
+
score = 80;
|
|
3331
|
+
}
|
|
3332
|
+
// Contains query gets medium score
|
|
3333
|
+
else if (fieldLower.includes(startsWithPattern)) {
|
|
3334
|
+
score = 50;
|
|
3335
|
+
}
|
|
3336
|
+
// Default score for regex match
|
|
3337
|
+
else {
|
|
3338
|
+
score = 10;
|
|
3339
|
+
}
|
|
3340
|
+
maxScore = Math.max(maxScore, score);
|
|
3341
|
+
}
|
|
3342
|
+
return { ...user, _searchScore: maxScore };
|
|
3343
|
+
});
|
|
3344
|
+
/** Top results sorted by relevance */
|
|
3345
|
+
return scoredUsers
|
|
3346
|
+
.sort((a, b) => b._searchScore - a._searchScore)
|
|
3347
|
+
.slice(0, limit)
|
|
3348
|
+
.map((user) => {
|
|
3349
|
+
// Remove the search score from final results
|
|
3350
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3351
|
+
const { _searchScore, ...userWithoutScore } = user;
|
|
3352
|
+
return userWithoutScore;
|
|
3353
|
+
});
|
|
3354
|
+
};
|
|
3355
|
+
return {
|
|
3356
|
+
findUser,
|
|
3357
|
+
countUsers,
|
|
3358
|
+
createUser,
|
|
3359
|
+
updateUser,
|
|
3360
|
+
searchUsers,
|
|
3361
|
+
getUserById,
|
|
3362
|
+
generateToken,
|
|
3363
|
+
deleteUserById,
|
|
3364
|
+
toggleUserMemories,
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
/**
|
|
3369
|
+
* Formats a date in YYYY-MM-DD format
|
|
3370
|
+
*/
|
|
3371
|
+
const formatDate = (date) => {
|
|
3372
|
+
return date.toISOString().split('T')[0];
|
|
3373
|
+
};
|
|
3374
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
3375
|
+
function createMemoryMethods(mongoose) {
|
|
3376
|
+
/**
|
|
3377
|
+
* Creates a new memory entry for a user
|
|
3378
|
+
* Throws an error if a memory with the same key already exists
|
|
3379
|
+
*/
|
|
3380
|
+
async function createMemory({ userId, key, value, tokenCount = 0, }) {
|
|
3381
|
+
try {
|
|
3382
|
+
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
3383
|
+
return { ok: false };
|
|
3384
|
+
}
|
|
3385
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
3386
|
+
const existingMemory = await MemoryEntry.findOne({ userId, key });
|
|
3387
|
+
if (existingMemory) {
|
|
3388
|
+
throw new Error('Memory with this key already exists');
|
|
3389
|
+
}
|
|
3390
|
+
await MemoryEntry.create({
|
|
3391
|
+
userId,
|
|
3392
|
+
key,
|
|
3393
|
+
value,
|
|
3394
|
+
tokenCount,
|
|
3395
|
+
updated_at: new Date(),
|
|
3396
|
+
});
|
|
3397
|
+
return { ok: true };
|
|
3398
|
+
}
|
|
3399
|
+
catch (error) {
|
|
3400
|
+
throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Sets or updates a memory entry for a user
|
|
3405
|
+
*/
|
|
3406
|
+
async function setMemory({ userId, key, value, tokenCount = 0, }) {
|
|
3407
|
+
try {
|
|
3408
|
+
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
3409
|
+
return { ok: false };
|
|
3410
|
+
}
|
|
3411
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
3412
|
+
await MemoryEntry.findOneAndUpdate({ userId, key }, {
|
|
3413
|
+
value,
|
|
3414
|
+
tokenCount,
|
|
3415
|
+
updated_at: new Date(),
|
|
3416
|
+
}, {
|
|
3417
|
+
upsert: true,
|
|
3418
|
+
new: true,
|
|
3419
|
+
});
|
|
3420
|
+
return { ok: true };
|
|
3421
|
+
}
|
|
3422
|
+
catch (error) {
|
|
3423
|
+
throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
/**
|
|
3427
|
+
* Deletes a specific memory entry for a user
|
|
3428
|
+
*/
|
|
3429
|
+
async function deleteMemory({ userId, key }) {
|
|
3430
|
+
try {
|
|
3431
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
3432
|
+
const result = await MemoryEntry.findOneAndDelete({ userId, key });
|
|
3433
|
+
return { ok: !!result };
|
|
3434
|
+
}
|
|
3435
|
+
catch (error) {
|
|
3436
|
+
throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Gets all memory entries for a user
|
|
3441
|
+
*/
|
|
3442
|
+
async function getAllUserMemories(userId) {
|
|
3443
|
+
try {
|
|
3444
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
3445
|
+
return (await MemoryEntry.find({ userId }).lean());
|
|
3446
|
+
}
|
|
3447
|
+
catch (error) {
|
|
3448
|
+
throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* Gets and formats all memories for a user in two different formats
|
|
3453
|
+
*/
|
|
3454
|
+
async function getFormattedMemories({ userId, }) {
|
|
3455
|
+
try {
|
|
3456
|
+
const memories = await getAllUserMemories(userId);
|
|
3457
|
+
if (!memories || memories.length === 0) {
|
|
3458
|
+
return { withKeys: '', withoutKeys: '', totalTokens: 0 };
|
|
3459
|
+
}
|
|
3460
|
+
const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
|
|
3461
|
+
const totalTokens = sortedMemories.reduce((sum, memory) => {
|
|
3462
|
+
return sum + (memory.tokenCount || 0);
|
|
3463
|
+
}, 0);
|
|
3464
|
+
const withKeys = sortedMemories
|
|
3465
|
+
.map((memory, index) => {
|
|
3466
|
+
const date = formatDate(new Date(memory.updated_at));
|
|
3467
|
+
const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
|
|
3468
|
+
return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
|
|
3469
|
+
})
|
|
3470
|
+
.join('\n\n');
|
|
3471
|
+
const withoutKeys = sortedMemories
|
|
3472
|
+
.map((memory, index) => {
|
|
3473
|
+
const date = formatDate(new Date(memory.updated_at));
|
|
3474
|
+
return `${index + 1}. [${date}]. ${memory.value}`;
|
|
3475
|
+
})
|
|
3476
|
+
.join('\n\n');
|
|
3477
|
+
return { withKeys, withoutKeys, totalTokens };
|
|
3478
|
+
}
|
|
3479
|
+
catch (error) {
|
|
3480
|
+
logger$1.error('Failed to get formatted memories:', error);
|
|
3481
|
+
return { withKeys: '', withoutKeys: '', totalTokens: 0 };
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
setMemory,
|
|
3486
|
+
createMemory,
|
|
3487
|
+
deleteMemory,
|
|
3488
|
+
getAllUserMemories,
|
|
3489
|
+
getFormattedMemories,
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function createAgentCategoryMethods(mongoose) {
|
|
3494
|
+
/**
|
|
3495
|
+
* Get all active categories sorted by order
|
|
3496
|
+
* @returns Array of active categories
|
|
3497
|
+
*/
|
|
3498
|
+
async function getActiveCategories() {
|
|
3499
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3500
|
+
return await AgentCategory.find({ isActive: true }).sort({ order: 1, label: 1 }).lean();
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* Get categories with agent counts
|
|
3504
|
+
* @returns Categories with agent counts
|
|
3505
|
+
*/
|
|
3506
|
+
async function getCategoriesWithCounts() {
|
|
3507
|
+
const Agent = mongoose.models.Agent;
|
|
3508
|
+
const categoryCounts = await Agent.aggregate([
|
|
3509
|
+
{ $match: { category: { $exists: true, $ne: null } } },
|
|
3510
|
+
{ $group: { _id: '$category', count: { $sum: 1 } } },
|
|
3511
|
+
]);
|
|
3512
|
+
const countMap = new Map(categoryCounts.map((c) => [c._id, c.count]));
|
|
3513
|
+
const categories = await getActiveCategories();
|
|
3514
|
+
return categories.map((category) => ({
|
|
3515
|
+
...category,
|
|
3516
|
+
agentCount: countMap.get(category.value) || 0,
|
|
3517
|
+
}));
|
|
3518
|
+
}
|
|
3519
|
+
/**
|
|
3520
|
+
* Get valid category values for Agent model validation
|
|
3521
|
+
* @returns Array of valid category values
|
|
3522
|
+
*/
|
|
3523
|
+
async function getValidCategoryValues() {
|
|
3524
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3525
|
+
return await AgentCategory.find({ isActive: true }).distinct('value').lean();
|
|
3526
|
+
}
|
|
3527
|
+
/**
|
|
3528
|
+
* Seed initial categories from existing constants
|
|
3529
|
+
* @param categories - Array of category data to seed
|
|
3530
|
+
* @returns Bulk write result
|
|
3531
|
+
*/
|
|
3532
|
+
async function seedCategories(categories) {
|
|
3533
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3534
|
+
const operations = categories.map((category, index) => ({
|
|
3535
|
+
updateOne: {
|
|
3536
|
+
filter: { value: category.value },
|
|
3537
|
+
update: {
|
|
3538
|
+
$setOnInsert: {
|
|
3539
|
+
value: category.value,
|
|
3540
|
+
label: category.label || category.value,
|
|
3541
|
+
description: category.description || '',
|
|
3542
|
+
order: category.order || index,
|
|
3543
|
+
isActive: true,
|
|
3544
|
+
},
|
|
3545
|
+
},
|
|
3546
|
+
upsert: true,
|
|
3547
|
+
},
|
|
3548
|
+
}));
|
|
3549
|
+
return await AgentCategory.bulkWrite(operations);
|
|
2215
3550
|
}
|
|
2216
3551
|
/**
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
3552
|
+
* Find a category by value
|
|
3553
|
+
* @param value - The category value to search for
|
|
3554
|
+
* @returns The category document or null
|
|
2219
3555
|
*/
|
|
2220
|
-
async function
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
3556
|
+
async function findCategoryByValue(value) {
|
|
3557
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3558
|
+
return await AgentCategory.findOne({ value }).lean();
|
|
3559
|
+
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Create a new category
|
|
3562
|
+
* @param categoryData - The category data to create
|
|
3563
|
+
* @returns The created category
|
|
3564
|
+
*/
|
|
3565
|
+
async function createCategory(categoryData) {
|
|
3566
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3567
|
+
const category = await AgentCategory.create(categoryData);
|
|
3568
|
+
return category.toObject();
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Update a category by value
|
|
3572
|
+
* @param value - The category value to update
|
|
3573
|
+
* @param updateData - The data to update
|
|
3574
|
+
* @returns The updated category or null
|
|
3575
|
+
*/
|
|
3576
|
+
async function updateCategory(value, updateData) {
|
|
3577
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3578
|
+
return await AgentCategory.findOneAndUpdate({ value }, { $set: updateData }, { new: true, runValidators: true }).lean();
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* Delete a category by value
|
|
3582
|
+
* @param value - The category value to delete
|
|
3583
|
+
* @returns Whether the deletion was successful
|
|
3584
|
+
*/
|
|
3585
|
+
async function deleteCategory(value) {
|
|
3586
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3587
|
+
const result = await AgentCategory.deleteOne({ value });
|
|
3588
|
+
return result.deletedCount > 0;
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* Find a category by ID
|
|
3592
|
+
* @param id - The category ID to search for
|
|
3593
|
+
* @returns The category document or null
|
|
3594
|
+
*/
|
|
3595
|
+
async function findCategoryById(id) {
|
|
3596
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3597
|
+
return await AgentCategory.findById(id).lean();
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Get all categories (active and inactive)
|
|
3601
|
+
* @returns Array of all categories
|
|
3602
|
+
*/
|
|
3603
|
+
async function getAllCategories() {
|
|
3604
|
+
const AgentCategory = mongoose.models.AgentCategory;
|
|
3605
|
+
return await AgentCategory.find({}).sort({ order: 1, label: 1 }).lean();
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Ensure default categories exist, seed them if none are present
|
|
3609
|
+
* @returns Promise<boolean> - true if categories were seeded, false if they already existed
|
|
3610
|
+
*/
|
|
3611
|
+
async function ensureDefaultCategories() {
|
|
3612
|
+
const existingCategories = await getAllCategories();
|
|
3613
|
+
if (existingCategories.length > 0) {
|
|
3614
|
+
return false; // Categories already exist
|
|
2226
3615
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
'
|
|
3616
|
+
const defaultCategories = [
|
|
3617
|
+
{
|
|
3618
|
+
value: 'general',
|
|
3619
|
+
label: 'General',
|
|
3620
|
+
description: 'General purpose agents for common tasks and inquiries',
|
|
3621
|
+
order: 0,
|
|
2231
3622
|
},
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
3623
|
+
{
|
|
3624
|
+
value: 'hr',
|
|
3625
|
+
label: 'Human Resources',
|
|
3626
|
+
description: 'Agents specialized in HR processes, policies, and employee support',
|
|
3627
|
+
order: 1,
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
value: 'rd',
|
|
3631
|
+
label: 'Research & Development',
|
|
3632
|
+
description: 'Agents focused on R&D processes, innovation, and technical research',
|
|
3633
|
+
order: 2,
|
|
3634
|
+
},
|
|
3635
|
+
{
|
|
3636
|
+
value: 'finance',
|
|
3637
|
+
label: 'Finance',
|
|
3638
|
+
description: 'Agents specialized in financial analysis, budgeting, and accounting',
|
|
3639
|
+
order: 3,
|
|
3640
|
+
},
|
|
3641
|
+
{
|
|
3642
|
+
value: 'it',
|
|
3643
|
+
label: 'IT',
|
|
3644
|
+
description: 'Agents for IT support, technical troubleshooting, and system administration',
|
|
3645
|
+
order: 4,
|
|
3646
|
+
},
|
|
3647
|
+
{
|
|
3648
|
+
value: 'sales',
|
|
3649
|
+
label: 'Sales',
|
|
3650
|
+
description: 'Agents focused on sales processes, customer relations.',
|
|
3651
|
+
order: 5,
|
|
3652
|
+
},
|
|
3653
|
+
{
|
|
3654
|
+
value: 'aftersales',
|
|
3655
|
+
label: 'After Sales',
|
|
3656
|
+
description: 'Agents specialized in post-sale support, maintenance, and customer service',
|
|
3657
|
+
order: 6,
|
|
3658
|
+
},
|
|
3659
|
+
];
|
|
3660
|
+
await seedCategories(defaultCategories);
|
|
3661
|
+
return true; // Categories were seeded
|
|
2237
3662
|
}
|
|
2238
|
-
// Return all methods
|
|
2239
3663
|
return {
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
3664
|
+
getActiveCategories,
|
|
3665
|
+
getCategoriesWithCounts,
|
|
3666
|
+
getValidCategoryValues,
|
|
3667
|
+
seedCategories,
|
|
3668
|
+
findCategoryByValue,
|
|
3669
|
+
createCategory,
|
|
3670
|
+
updateCategory,
|
|
3671
|
+
deleteCategory,
|
|
3672
|
+
findCategoryById,
|
|
3673
|
+
getAllCategories,
|
|
3674
|
+
ensureDefaultCategories,
|
|
2248
3675
|
};
|
|
2249
3676
|
}
|
|
2250
3677
|
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
/**
|
|
2269
|
-
* Determines if a given value string is sensitive and returns matching regex patterns.
|
|
2270
|
-
*
|
|
2271
|
-
* @param valueStr - The value string to check.
|
|
2272
|
-
* @returns An array of regex patterns that match the value string.
|
|
2273
|
-
*/
|
|
2274
|
-
function getMatchingSensitivePatterns(valueStr) {
|
|
2275
|
-
if (valueStr) {
|
|
2276
|
-
// Filter and return all regex patterns that match the value string
|
|
2277
|
-
return sensitiveKeys.filter((regex) => regex.test(valueStr));
|
|
3678
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
3679
|
+
function createPluginAuthMethods(mongoose) {
|
|
3680
|
+
/**
|
|
3681
|
+
* Finds a single plugin auth entry by userId and authField (and optionally pluginKey)
|
|
3682
|
+
*/
|
|
3683
|
+
async function findOnePluginAuth({ userId, authField, pluginKey, }) {
|
|
3684
|
+
try {
|
|
3685
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3686
|
+
return await PluginAuth.findOne({
|
|
3687
|
+
userId,
|
|
3688
|
+
authField,
|
|
3689
|
+
...(pluginKey && { pluginKey }),
|
|
3690
|
+
}).lean();
|
|
3691
|
+
}
|
|
3692
|
+
catch (error) {
|
|
3693
|
+
throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3694
|
+
}
|
|
2278
3695
|
}
|
|
2279
|
-
|
|
3696
|
+
/**
|
|
3697
|
+
* Finds multiple plugin auth entries by userId and pluginKeys
|
|
3698
|
+
*/
|
|
3699
|
+
async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
|
|
3700
|
+
try {
|
|
3701
|
+
if (!pluginKeys || pluginKeys.length === 0) {
|
|
3702
|
+
return [];
|
|
3703
|
+
}
|
|
3704
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3705
|
+
return await PluginAuth.find({
|
|
3706
|
+
userId,
|
|
3707
|
+
pluginKey: { $in: pluginKeys },
|
|
3708
|
+
}).lean();
|
|
3709
|
+
}
|
|
3710
|
+
catch (error) {
|
|
3711
|
+
throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Updates or creates a plugin auth entry
|
|
3716
|
+
*/
|
|
3717
|
+
async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
|
|
3718
|
+
try {
|
|
3719
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3720
|
+
const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
|
|
3721
|
+
if (existingAuth) {
|
|
3722
|
+
return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
|
|
3723
|
+
}
|
|
3724
|
+
else {
|
|
3725
|
+
const newPluginAuth = await new PluginAuth({
|
|
3726
|
+
userId,
|
|
3727
|
+
authField,
|
|
3728
|
+
value,
|
|
3729
|
+
pluginKey,
|
|
3730
|
+
});
|
|
3731
|
+
await newPluginAuth.save();
|
|
3732
|
+
return newPluginAuth.toObject();
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
catch (error) {
|
|
3736
|
+
throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Deletes plugin auth entries based on provided parameters
|
|
3741
|
+
*/
|
|
3742
|
+
async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
|
|
3743
|
+
try {
|
|
3744
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3745
|
+
if (all) {
|
|
3746
|
+
const filter = { userId };
|
|
3747
|
+
if (pluginKey) {
|
|
3748
|
+
filter.pluginKey = pluginKey;
|
|
3749
|
+
}
|
|
3750
|
+
return await PluginAuth.deleteMany(filter);
|
|
3751
|
+
}
|
|
3752
|
+
if (!authField) {
|
|
3753
|
+
throw new Error('authField is required when all is false');
|
|
3754
|
+
}
|
|
3755
|
+
return await PluginAuth.deleteOne({ userId, authField });
|
|
3756
|
+
}
|
|
3757
|
+
catch (error) {
|
|
3758
|
+
throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Deletes all plugin auth entries for a user
|
|
3763
|
+
*/
|
|
3764
|
+
async function deleteAllUserPluginAuths(userId) {
|
|
3765
|
+
try {
|
|
3766
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3767
|
+
return await PluginAuth.deleteMany({ userId });
|
|
3768
|
+
}
|
|
3769
|
+
catch (error) {
|
|
3770
|
+
throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
return {
|
|
3774
|
+
findOnePluginAuth,
|
|
3775
|
+
findPluginAuthsByKeys,
|
|
3776
|
+
updatePluginAuth,
|
|
3777
|
+
deletePluginAuth,
|
|
3778
|
+
deleteAllUserPluginAuths,
|
|
3779
|
+
};
|
|
2280
3780
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
3781
|
+
|
|
3782
|
+
function createAccessRoleMethods(mongoose) {
|
|
3783
|
+
/**
|
|
3784
|
+
* Find an access role by its ID
|
|
3785
|
+
* @param roleId - The role ID
|
|
3786
|
+
* @returns The role document or null if not found
|
|
3787
|
+
*/
|
|
3788
|
+
async function findRoleById(roleId) {
|
|
3789
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3790
|
+
return await AccessRole.findById(roleId).lean();
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Find an access role by its unique identifier
|
|
3794
|
+
* @param accessRoleId - The unique identifier (e.g., "agent_viewer")
|
|
3795
|
+
* @returns The role document or null if not found
|
|
3796
|
+
*/
|
|
3797
|
+
async function findRoleByIdentifier(accessRoleId) {
|
|
3798
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3799
|
+
return await AccessRole.findOne({ accessRoleId }).lean();
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Find all access roles for a specific resource type
|
|
3803
|
+
* @param resourceType - The type of resource ('agent', 'project', 'file')
|
|
3804
|
+
* @returns Array of role documents
|
|
3805
|
+
*/
|
|
3806
|
+
async function findRolesByResourceType(resourceType) {
|
|
3807
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3808
|
+
return await AccessRole.find({ resourceType }).lean();
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Find an access role by resource type and permission bits
|
|
3812
|
+
* @param resourceType - The type of resource
|
|
3813
|
+
* @param permBits - The permission bits (use PermissionBits or RoleBits enum)
|
|
3814
|
+
* @returns The role document or null if not found
|
|
3815
|
+
*/
|
|
3816
|
+
async function findRoleByPermissions(resourceType, permBits) {
|
|
3817
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3818
|
+
return await AccessRole.findOne({ resourceType, permBits }).lean();
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Create a new access role
|
|
3822
|
+
* @param roleData - Role data (accessRoleId, name, description, resourceType, permBits)
|
|
3823
|
+
* @returns The created role document
|
|
3824
|
+
*/
|
|
3825
|
+
async function createRole(roleData) {
|
|
3826
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3827
|
+
return await AccessRole.create(roleData);
|
|
2290
3828
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
* @param info - The log information object.
|
|
2301
|
-
* @returns The modified log information object.
|
|
2302
|
-
*/
|
|
2303
|
-
const redactFormat = winston.format((info) => {
|
|
2304
|
-
if (info.level === 'error') {
|
|
2305
|
-
// Type guard to ensure message is a string
|
|
2306
|
-
if (typeof info.message === 'string') {
|
|
2307
|
-
info.message = redactMessage(info.message);
|
|
2308
|
-
}
|
|
2309
|
-
// Handle MESSAGE_SYMBOL with type safety
|
|
2310
|
-
const symbolValue = info[MESSAGE_SYMBOL];
|
|
2311
|
-
if (typeof symbolValue === 'string') {
|
|
2312
|
-
info[MESSAGE_SYMBOL] = redactMessage(symbolValue);
|
|
2313
|
-
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Update an existing access role
|
|
3831
|
+
* @param accessRoleId - The unique identifier of the role to update
|
|
3832
|
+
* @param updateData - Data to update
|
|
3833
|
+
* @returns The updated role document or null if not found
|
|
3834
|
+
*/
|
|
3835
|
+
async function updateRole(accessRoleId, updateData) {
|
|
3836
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3837
|
+
return await AccessRole.findOneAndUpdate({ accessRoleId }, { $set: updateData }, { new: true }).lean();
|
|
2314
3838
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
*/
|
|
2324
|
-
const truncateLongStrings = (value, length = 100) => {
|
|
2325
|
-
if (typeof value === 'string') {
|
|
2326
|
-
return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
|
|
3839
|
+
/**
|
|
3840
|
+
* Delete an access role
|
|
3841
|
+
* @param accessRoleId - The unique identifier of the role to delete
|
|
3842
|
+
* @returns The result of the delete operation
|
|
3843
|
+
*/
|
|
3844
|
+
async function deleteRole(accessRoleId) {
|
|
3845
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3846
|
+
return await AccessRole.deleteOne({ accessRoleId });
|
|
2327
3847
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
const condenseArray = (item) => {
|
|
2336
|
-
if (typeof item === 'string') {
|
|
2337
|
-
return truncateLongStrings(JSON.stringify(item));
|
|
3848
|
+
/**
|
|
3849
|
+
* Get all predefined roles
|
|
3850
|
+
* @returns Array of all role documents
|
|
3851
|
+
*/
|
|
3852
|
+
async function getAllRoles() {
|
|
3853
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3854
|
+
return await AccessRole.find().lean();
|
|
2338
3855
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
3856
|
+
/**
|
|
3857
|
+
* Seed default roles if they don't exist
|
|
3858
|
+
* @returns Object containing created roles
|
|
3859
|
+
*/
|
|
3860
|
+
async function seedDefaultRoles() {
|
|
3861
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3862
|
+
const defaultRoles = [
|
|
3863
|
+
{
|
|
3864
|
+
accessRoleId: AccessRoleIds.AGENT_VIEWER,
|
|
3865
|
+
name: 'com_ui_role_viewer',
|
|
3866
|
+
description: 'com_ui_role_viewer_desc',
|
|
3867
|
+
resourceType: ResourceType.AGENT,
|
|
3868
|
+
permBits: RoleBits.VIEWER,
|
|
3869
|
+
},
|
|
3870
|
+
{
|
|
3871
|
+
accessRoleId: AccessRoleIds.AGENT_EDITOR,
|
|
3872
|
+
name: 'com_ui_role_editor',
|
|
3873
|
+
description: 'com_ui_role_editor_desc',
|
|
3874
|
+
resourceType: ResourceType.AGENT,
|
|
3875
|
+
permBits: RoleBits.EDITOR,
|
|
3876
|
+
},
|
|
3877
|
+
{
|
|
3878
|
+
accessRoleId: AccessRoleIds.AGENT_OWNER,
|
|
3879
|
+
name: 'com_ui_role_owner',
|
|
3880
|
+
description: 'com_ui_role_owner_desc',
|
|
3881
|
+
resourceType: ResourceType.AGENT,
|
|
3882
|
+
permBits: RoleBits.OWNER,
|
|
3883
|
+
},
|
|
3884
|
+
{
|
|
3885
|
+
accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER,
|
|
3886
|
+
name: 'com_ui_role_viewer',
|
|
3887
|
+
description: 'com_ui_role_viewer_desc',
|
|
3888
|
+
resourceType: ResourceType.PROMPTGROUP,
|
|
3889
|
+
permBits: RoleBits.VIEWER,
|
|
3890
|
+
},
|
|
3891
|
+
{
|
|
3892
|
+
accessRoleId: AccessRoleIds.PROMPTGROUP_EDITOR,
|
|
3893
|
+
name: 'com_ui_role_editor',
|
|
3894
|
+
description: 'com_ui_role_editor_desc',
|
|
3895
|
+
resourceType: ResourceType.PROMPTGROUP,
|
|
3896
|
+
permBits: RoleBits.EDITOR,
|
|
3897
|
+
},
|
|
3898
|
+
{
|
|
3899
|
+
accessRoleId: AccessRoleIds.PROMPTGROUP_OWNER,
|
|
3900
|
+
name: 'com_ui_role_owner',
|
|
3901
|
+
description: 'com_ui_role_owner_desc',
|
|
3902
|
+
resourceType: ResourceType.PROMPTGROUP,
|
|
3903
|
+
permBits: RoleBits.OWNER,
|
|
3904
|
+
},
|
|
3905
|
+
];
|
|
3906
|
+
const result = {};
|
|
3907
|
+
for (const role of defaultRoles) {
|
|
3908
|
+
const upsertedRole = await AccessRole.findOneAndUpdate({ accessRoleId: role.accessRoleId }, { $setOnInsert: role }, { upsert: true, new: true }).lean();
|
|
3909
|
+
result[role.accessRoleId] = upsertedRole;
|
|
3910
|
+
}
|
|
3911
|
+
return result;
|
|
2341
3912
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
return
|
|
3913
|
+
/**
|
|
3914
|
+
* Helper to get the appropriate role for a set of permissions
|
|
3915
|
+
* @param resourceType - The type of resource
|
|
3916
|
+
* @param permBits - The permission bits
|
|
3917
|
+
* @returns The matching role or null if none found
|
|
3918
|
+
*/
|
|
3919
|
+
async function getRoleForPermissions(resourceType, permBits) {
|
|
3920
|
+
const AccessRole = mongoose.models.AccessRole;
|
|
3921
|
+
const exactMatch = await AccessRole.findOne({ resourceType, permBits }).lean();
|
|
3922
|
+
if (exactMatch) {
|
|
3923
|
+
return exactMatch;
|
|
3924
|
+
}
|
|
3925
|
+
/** If no exact match, the closest role without exceeding permissions */
|
|
3926
|
+
const roles = await AccessRole.find({ resourceType }).sort({ permBits: -1 }).lean();
|
|
3927
|
+
return roles.find((role) => (role.permBits & permBits) === role.permBits) || null;
|
|
2357
3928
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
3929
|
+
return {
|
|
3930
|
+
createRole,
|
|
3931
|
+
updateRole,
|
|
3932
|
+
deleteRole,
|
|
3933
|
+
getAllRoles,
|
|
3934
|
+
findRoleById,
|
|
3935
|
+
seedDefaultRoles,
|
|
3936
|
+
findRoleByIdentifier,
|
|
3937
|
+
getRoleForPermissions,
|
|
3938
|
+
findRoleByPermissions,
|
|
3939
|
+
findRolesByResourceType,
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
function createUserGroupMethods(mongoose) {
|
|
3944
|
+
/**
|
|
3945
|
+
* Find a group by its ID
|
|
3946
|
+
* @param groupId - The group ID
|
|
3947
|
+
* @param projection - Optional projection of fields to return
|
|
3948
|
+
* @param session - Optional MongoDB session for transactions
|
|
3949
|
+
* @returns The group document or null if not found
|
|
3950
|
+
*/
|
|
3951
|
+
async function findGroupById(groupId, projection = {}, session) {
|
|
3952
|
+
const Group = mongoose.models.Group;
|
|
3953
|
+
const query = Group.findOne({ _id: groupId }, projection);
|
|
3954
|
+
if (session) {
|
|
3955
|
+
query.session(session);
|
|
3956
|
+
}
|
|
3957
|
+
return await query.lean();
|
|
2361
3958
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
3959
|
+
/**
|
|
3960
|
+
* Find a group by its external ID (e.g., Entra ID)
|
|
3961
|
+
* @param idOnTheSource - The external ID
|
|
3962
|
+
* @param source - The source ('entra' or 'local')
|
|
3963
|
+
* @param projection - Optional projection of fields to return
|
|
3964
|
+
* @param session - Optional MongoDB session for transactions
|
|
3965
|
+
* @returns The group document or null if not found
|
|
3966
|
+
*/
|
|
3967
|
+
async function findGroupByExternalId(idOnTheSource, source = 'entra', projection = {}, session) {
|
|
3968
|
+
const Group = mongoose.models.Group;
|
|
3969
|
+
const query = Group.findOne({ idOnTheSource, source }, projection);
|
|
3970
|
+
if (session) {
|
|
3971
|
+
query.session(session);
|
|
2366
3972
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
3973
|
+
return await query.lean();
|
|
3974
|
+
}
|
|
3975
|
+
/**
|
|
3976
|
+
* Find groups by name pattern (case-insensitive partial match)
|
|
3977
|
+
* @param namePattern - The name pattern to search for
|
|
3978
|
+
* @param source - Optional source filter ('entra', 'local', or null for all)
|
|
3979
|
+
* @param limit - Maximum number of results to return
|
|
3980
|
+
* @param session - Optional MongoDB session for transactions
|
|
3981
|
+
* @returns Array of matching groups
|
|
3982
|
+
*/
|
|
3983
|
+
async function findGroupsByNamePattern(namePattern, source = null, limit = 20, session) {
|
|
3984
|
+
const Group = mongoose.models.Group;
|
|
3985
|
+
const regex = new RegExp(namePattern, 'i');
|
|
3986
|
+
const query = {
|
|
3987
|
+
$or: [{ name: regex }, { email: regex }, { description: regex }],
|
|
3988
|
+
};
|
|
3989
|
+
if (source) {
|
|
3990
|
+
query.source = source;
|
|
2369
3991
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const debugValue = Array.isArray(splatArray) ? splatArray[0] : undefined;
|
|
2374
|
-
if (!debugValue) {
|
|
2375
|
-
return msg;
|
|
3992
|
+
const dbQuery = Group.find(query).limit(limit);
|
|
3993
|
+
if (session) {
|
|
3994
|
+
dbQuery.session(session);
|
|
2376
3995
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
3996
|
+
return await dbQuery.lean();
|
|
3997
|
+
}
|
|
3998
|
+
/**
|
|
3999
|
+
* Find all groups a user is a member of by their ID or idOnTheSource
|
|
4000
|
+
* @param userId - The user ID
|
|
4001
|
+
* @param session - Optional MongoDB session for transactions
|
|
4002
|
+
* @returns Array of groups the user is a member of
|
|
4003
|
+
*/
|
|
4004
|
+
async function findGroupsByMemberId(userId, session) {
|
|
4005
|
+
const User = mongoose.models.User;
|
|
4006
|
+
const Group = mongoose.models.Group;
|
|
4007
|
+
const userQuery = User.findById(userId, 'idOnTheSource');
|
|
4008
|
+
if (session) {
|
|
4009
|
+
userQuery.session(session);
|
|
2380
4010
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
4011
|
+
const user = (await userQuery.lean());
|
|
4012
|
+
if (!user) {
|
|
4013
|
+
return [];
|
|
2383
4014
|
}
|
|
2384
|
-
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
}
|
|
2391
|
-
let _parentKey = '';
|
|
2392
|
-
const parent = this.parent;
|
|
2393
|
-
if (typeof (parent === null || parent === void 0 ? void 0 : parent.key) !== 'symbol' && (parent === null || parent === void 0 ? void 0 : parent.key)) {
|
|
2394
|
-
_parentKey = parent.key;
|
|
2395
|
-
}
|
|
2396
|
-
const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
|
|
2397
|
-
const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
|
|
2398
|
-
const currentKey = (_a = this === null || this === void 0 ? void 0 : this.key) !== null && _a !== void 0 ? _a : 'unknown';
|
|
2399
|
-
if (this.isLeaf && typeof value === 'string') {
|
|
2400
|
-
const truncatedText = truncateLongStrings(value);
|
|
2401
|
-
msg += `\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`;
|
|
2402
|
-
}
|
|
2403
|
-
else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
|
|
2404
|
-
const currentMessage = `\n${tabs}// ${value.length} ${currentKey.replace(/s$/, '')}(s)`;
|
|
2405
|
-
this.update(currentMessage, true);
|
|
2406
|
-
msg += currentMessage;
|
|
2407
|
-
const stringifiedArray = value.map(condenseArray);
|
|
2408
|
-
msg += `\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`;
|
|
2409
|
-
}
|
|
2410
|
-
else if (this.isLeaf && typeof value === 'function') {
|
|
2411
|
-
msg += `\n${tabs}${parentKey}${currentKey}: function,`;
|
|
2412
|
-
}
|
|
2413
|
-
else if (this.isLeaf) {
|
|
2414
|
-
msg += `\n${tabs}${parentKey}${currentKey}: ${value},`;
|
|
2415
|
-
}
|
|
2416
|
-
});
|
|
2417
|
-
msg += '\n}';
|
|
2418
|
-
return msg;
|
|
4015
|
+
const userIdOnTheSource = user.idOnTheSource || userId.toString();
|
|
4016
|
+
const query = Group.find({ memberIds: userIdOnTheSource });
|
|
4017
|
+
if (session) {
|
|
4018
|
+
query.session(session);
|
|
4019
|
+
}
|
|
4020
|
+
return await query.lean();
|
|
2419
4021
|
}
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
4022
|
+
/**
|
|
4023
|
+
* Create a new group
|
|
4024
|
+
* @param groupData - Group data including name, source, and optional idOnTheSource
|
|
4025
|
+
* @param session - Optional MongoDB session for transactions
|
|
4026
|
+
* @returns The created group
|
|
4027
|
+
*/
|
|
4028
|
+
async function createGroup(groupData, session) {
|
|
4029
|
+
const Group = mongoose.models.Group;
|
|
4030
|
+
const options = session ? { session } : {};
|
|
4031
|
+
return await Group.create([groupData], options).then((groups) => groups[0]);
|
|
2423
4032
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
4033
|
+
/**
|
|
4034
|
+
* Update or create a group by external ID
|
|
4035
|
+
* @param idOnTheSource - The external ID
|
|
4036
|
+
* @param source - The source ('entra' or 'local')
|
|
4037
|
+
* @param updateData - Data to update or set if creating
|
|
4038
|
+
* @param session - Optional MongoDB session for transactions
|
|
4039
|
+
* @returns The updated or created group
|
|
4040
|
+
*/
|
|
4041
|
+
async function upsertGroupByExternalId(idOnTheSource, source, updateData, session) {
|
|
4042
|
+
const Group = mongoose.models.Group;
|
|
4043
|
+
const options = {
|
|
4044
|
+
new: true,
|
|
4045
|
+
upsert: true,
|
|
4046
|
+
...(session ? { session } : {}),
|
|
4047
|
+
};
|
|
4048
|
+
return await Group.findOneAndUpdate({ idOnTheSource, source }, { $set: updateData }, options);
|
|
4049
|
+
}
|
|
4050
|
+
/**
|
|
4051
|
+
* Add a user to a group
|
|
4052
|
+
* Only updates Group.memberIds (one-way relationship)
|
|
4053
|
+
* Note: memberIds stores idOnTheSource values, not ObjectIds
|
|
4054
|
+
*
|
|
4055
|
+
* @param userId - The user ID
|
|
4056
|
+
* @param groupId - The group ID to add
|
|
4057
|
+
* @param session - Optional MongoDB session for transactions
|
|
4058
|
+
* @returns The user and updated group documents
|
|
4059
|
+
*/
|
|
4060
|
+
async function addUserToGroup(userId, groupId, session) {
|
|
4061
|
+
const User = mongoose.models.User;
|
|
4062
|
+
const Group = mongoose.models.Group;
|
|
4063
|
+
const options = { new: true, ...(session ? { session } : {}) };
|
|
4064
|
+
const user = (await User.findById(userId, 'idOnTheSource', options).lean());
|
|
4065
|
+
if (!user) {
|
|
4066
|
+
throw new Error(`User not found: ${userId}`);
|
|
2439
4067
|
}
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
4068
|
+
const userIdOnTheSource = user.idOnTheSource || userId.toString();
|
|
4069
|
+
const updatedGroup = await Group.findByIdAndUpdate(groupId, { $addToSet: { memberIds: userIdOnTheSource } }, options).lean();
|
|
4070
|
+
return { user: user, group: updatedGroup };
|
|
4071
|
+
}
|
|
4072
|
+
/**
|
|
4073
|
+
* Remove a user from a group
|
|
4074
|
+
* Only updates Group.memberIds (one-way relationship)
|
|
4075
|
+
* Note: memberIds stores idOnTheSource values, not ObjectIds
|
|
4076
|
+
*
|
|
4077
|
+
* @param userId - The user ID
|
|
4078
|
+
* @param groupId - The group ID to remove
|
|
4079
|
+
* @param session - Optional MongoDB session for transactions
|
|
4080
|
+
* @returns The user and updated group documents
|
|
4081
|
+
*/
|
|
4082
|
+
async function removeUserFromGroup(userId, groupId, session) {
|
|
4083
|
+
const User = mongoose.models.User;
|
|
4084
|
+
const Group = mongoose.models.Group;
|
|
4085
|
+
const options = { new: true, ...(session ? { session } : {}) };
|
|
4086
|
+
const user = (await User.findById(userId, 'idOnTheSource', options).lean());
|
|
4087
|
+
if (!user) {
|
|
4088
|
+
throw new Error(`User not found: ${userId}`);
|
|
2443
4089
|
}
|
|
2444
|
-
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
return
|
|
2456
|
-
};
|
|
2457
|
-
return truncateObject(info);
|
|
2458
|
-
});
|
|
2459
|
-
|
|
2460
|
-
const logDir = getLogDirectory();
|
|
2461
|
-
const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
|
|
2462
|
-
const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
|
|
2463
|
-
const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
|
|
2464
|
-
const useDebugLogging = typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true';
|
|
2465
|
-
const levels = {
|
|
2466
|
-
error: 0,
|
|
2467
|
-
warn: 1,
|
|
2468
|
-
info: 2,
|
|
2469
|
-
http: 3,
|
|
2470
|
-
verbose: 4,
|
|
2471
|
-
debug: 5,
|
|
2472
|
-
activity: 6,
|
|
2473
|
-
silly: 7,
|
|
2474
|
-
};
|
|
2475
|
-
winston.addColors({
|
|
2476
|
-
info: 'green',
|
|
2477
|
-
warn: 'italic yellow',
|
|
2478
|
-
error: 'red',
|
|
2479
|
-
debug: 'blue',
|
|
2480
|
-
});
|
|
2481
|
-
const level = () => {
|
|
2482
|
-
const env = NODE_ENV || 'development';
|
|
2483
|
-
return env === 'development' ? 'debug' : 'warn';
|
|
2484
|
-
};
|
|
2485
|
-
const fileFormat = winston.format.combine(redactFormat(), winston.format.timestamp({ format: () => new Date().toISOString() }), winston.format.errors({ stack: true }), winston.format.splat());
|
|
2486
|
-
const transports = [
|
|
2487
|
-
new winston.transports.DailyRotateFile({
|
|
2488
|
-
level: 'error',
|
|
2489
|
-
filename: `${logDir}/error-%DATE%.log`,
|
|
2490
|
-
datePattern: 'YYYY-MM-DD',
|
|
2491
|
-
zippedArchive: true,
|
|
2492
|
-
maxSize: '20m',
|
|
2493
|
-
maxFiles: '14d',
|
|
2494
|
-
format: fileFormat,
|
|
2495
|
-
}),
|
|
2496
|
-
];
|
|
2497
|
-
if (useDebugLogging) {
|
|
2498
|
-
transports.push(new winston.transports.DailyRotateFile({
|
|
2499
|
-
level: 'debug',
|
|
2500
|
-
filename: `${logDir}/debug-%DATE%.log`,
|
|
2501
|
-
datePattern: 'YYYY-MM-DD',
|
|
2502
|
-
zippedArchive: true,
|
|
2503
|
-
maxSize: '20m',
|
|
2504
|
-
maxFiles: '14d',
|
|
2505
|
-
format: winston.format.combine(fileFormat, debugTraverse),
|
|
2506
|
-
}));
|
|
2507
|
-
}
|
|
2508
|
-
const consoleFormat = winston.format.combine(redactFormat(), winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
|
|
2509
|
-
const message = `${info.timestamp} ${info.level}: ${info.message}`;
|
|
2510
|
-
return info.level.includes('error') ? redactMessage(message) : message;
|
|
2511
|
-
}));
|
|
2512
|
-
let consoleLogLevel = 'info';
|
|
2513
|
-
if (useDebugConsole) {
|
|
2514
|
-
consoleLogLevel = 'debug';
|
|
2515
|
-
}
|
|
2516
|
-
// Add console transport
|
|
2517
|
-
if (useDebugConsole) {
|
|
2518
|
-
transports.push(new winston.transports.Console({
|
|
2519
|
-
level: consoleLogLevel,
|
|
2520
|
-
format: useConsoleJson
|
|
2521
|
-
? winston.format.combine(fileFormat, jsonTruncateFormat(), winston.format.json())
|
|
2522
|
-
: winston.format.combine(fileFormat, debugTraverse),
|
|
2523
|
-
}));
|
|
2524
|
-
}
|
|
2525
|
-
else if (useConsoleJson) {
|
|
2526
|
-
transports.push(new winston.transports.Console({
|
|
2527
|
-
level: consoleLogLevel,
|
|
2528
|
-
format: winston.format.combine(fileFormat, jsonTruncateFormat(), winston.format.json()),
|
|
2529
|
-
}));
|
|
2530
|
-
}
|
|
2531
|
-
else {
|
|
2532
|
-
transports.push(new winston.transports.Console({
|
|
2533
|
-
level: consoleLogLevel,
|
|
2534
|
-
format: consoleFormat,
|
|
2535
|
-
}));
|
|
2536
|
-
}
|
|
2537
|
-
// Create logger
|
|
2538
|
-
const logger = winston.createLogger({
|
|
2539
|
-
level: level(),
|
|
2540
|
-
levels,
|
|
2541
|
-
transports,
|
|
2542
|
-
});
|
|
2543
|
-
|
|
2544
|
-
var _a;
|
|
2545
|
-
class SessionError extends Error {
|
|
2546
|
-
constructor(message, code = 'SESSION_ERROR') {
|
|
2547
|
-
super(message);
|
|
2548
|
-
this.name = 'SessionError';
|
|
2549
|
-
this.code = code;
|
|
4090
|
+
const userIdOnTheSource = user.idOnTheSource || userId.toString();
|
|
4091
|
+
const updatedGroup = await Group.findByIdAndUpdate(groupId, { $pull: { memberIds: userIdOnTheSource } }, options).lean();
|
|
4092
|
+
return { user: user, group: updatedGroup };
|
|
4093
|
+
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Get all groups a user is a member of
|
|
4096
|
+
* @param userId - The user ID
|
|
4097
|
+
* @param session - Optional MongoDB session for transactions
|
|
4098
|
+
* @returns Array of group documents
|
|
4099
|
+
*/
|
|
4100
|
+
async function getUserGroups(userId, session) {
|
|
4101
|
+
return await findGroupsByMemberId(userId, session);
|
|
2550
4102
|
}
|
|
2551
|
-
}
|
|
2552
|
-
const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
|
|
2553
|
-
const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
2554
|
-
// Factory function that takes mongoose instance and returns the methods
|
|
2555
|
-
function createSessionMethods(mongoose) {
|
|
2556
4103
|
/**
|
|
2557
|
-
*
|
|
4104
|
+
* Get a list of all principal identifiers for a user (user ID + group IDs + public)
|
|
4105
|
+
* For use in permission checks
|
|
4106
|
+
* @param params - Parameters object
|
|
4107
|
+
* @param params.userId - The user ID
|
|
4108
|
+
* @param params.role - Optional user role (if not provided, will query from DB)
|
|
4109
|
+
* @param session - Optional MongoDB session for transactions
|
|
4110
|
+
* @returns Array of principal objects with type and id
|
|
2558
4111
|
*/
|
|
2559
|
-
async function
|
|
2560
|
-
|
|
2561
|
-
|
|
4112
|
+
async function getUserPrincipals(params, session) {
|
|
4113
|
+
const { userId, role } = params;
|
|
4114
|
+
/** `userId` must be an `ObjectId` for USER principal since ACL entries store `ObjectId`s */
|
|
4115
|
+
const userObjectId = typeof userId === 'string' ? new Types.ObjectId(userId) : userId;
|
|
4116
|
+
const principals = [
|
|
4117
|
+
{ principalType: PrincipalType.USER, principalId: userObjectId },
|
|
4118
|
+
];
|
|
4119
|
+
// If role is not provided, query user to get it
|
|
4120
|
+
let userRole = role;
|
|
4121
|
+
if (userRole === undefined) {
|
|
4122
|
+
const User = mongoose.models.User;
|
|
4123
|
+
const query = User.findById(userId).select('role');
|
|
4124
|
+
if (session) {
|
|
4125
|
+
query.session(session);
|
|
4126
|
+
}
|
|
4127
|
+
const user = await query.lean();
|
|
4128
|
+
userRole = user === null || user === void 0 ? void 0 : user.role;
|
|
2562
4129
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
user: userId,
|
|
2567
|
-
expiration: options.expiration || new Date(Date.now() + expires),
|
|
2568
|
-
});
|
|
2569
|
-
const refreshToken = await generateRefreshToken(currentSession);
|
|
2570
|
-
return { session: currentSession, refreshToken };
|
|
4130
|
+
// Add role as a principal if user has one
|
|
4131
|
+
if (userRole && userRole.trim()) {
|
|
4132
|
+
principals.push({ principalType: PrincipalType.ROLE, principalId: userRole });
|
|
2571
4133
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
4134
|
+
const userGroups = await getUserGroups(userId, session);
|
|
4135
|
+
if (userGroups && userGroups.length > 0) {
|
|
4136
|
+
userGroups.forEach((group) => {
|
|
4137
|
+
principals.push({ principalType: PrincipalType.GROUP, principalId: group._id });
|
|
4138
|
+
});
|
|
2575
4139
|
}
|
|
4140
|
+
principals.push({ principalType: PrincipalType.PUBLIC });
|
|
4141
|
+
return principals;
|
|
2576
4142
|
}
|
|
2577
4143
|
/**
|
|
2578
|
-
*
|
|
4144
|
+
* Sync a user's Entra ID group memberships
|
|
4145
|
+
* @param userId - The user ID
|
|
4146
|
+
* @param entraGroups - Array of Entra groups with id and name
|
|
4147
|
+
* @param session - Optional MongoDB session for transactions
|
|
4148
|
+
* @returns The updated user with new group memberships
|
|
2579
4149
|
*/
|
|
2580
|
-
async function
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
|
|
4150
|
+
async function syncUserEntraGroups(userId, entraGroups, session) {
|
|
4151
|
+
var _a;
|
|
4152
|
+
const User = mongoose.models.User;
|
|
4153
|
+
const Group = mongoose.models.Group;
|
|
4154
|
+
const query = User.findById(userId, { idOnTheSource: 1 });
|
|
4155
|
+
if (session) {
|
|
4156
|
+
query.session(session);
|
|
4157
|
+
}
|
|
4158
|
+
const user = (await query.lean());
|
|
4159
|
+
if (!user) {
|
|
4160
|
+
throw new Error(`User not found: ${userId}`);
|
|
4161
|
+
}
|
|
4162
|
+
/** Get user's idOnTheSource for storing in group.memberIds */
|
|
4163
|
+
const userIdOnTheSource = user.idOnTheSource || userId.toString();
|
|
4164
|
+
const entraIdMap = new Map();
|
|
4165
|
+
const addedGroups = [];
|
|
4166
|
+
const removedGroups = [];
|
|
4167
|
+
for (const entraGroup of entraGroups) {
|
|
4168
|
+
entraIdMap.set(entraGroup.id, true);
|
|
4169
|
+
let group = await findGroupByExternalId(entraGroup.id, 'entra', {}, session);
|
|
4170
|
+
if (!group) {
|
|
4171
|
+
group = await createGroup({
|
|
4172
|
+
name: entraGroup.name,
|
|
4173
|
+
description: entraGroup.description,
|
|
4174
|
+
email: entraGroup.email,
|
|
4175
|
+
idOnTheSource: entraGroup.id,
|
|
4176
|
+
source: 'entra',
|
|
4177
|
+
memberIds: [userIdOnTheSource],
|
|
4178
|
+
}, session);
|
|
4179
|
+
addedGroups.push(group);
|
|
2593
4180
|
}
|
|
2594
|
-
if (
|
|
2595
|
-
const
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
? params.sessionId.sessionId
|
|
2599
|
-
: params.sessionId;
|
|
2600
|
-
if (!mongoose.Types.ObjectId.isValid(sessionId)) {
|
|
2601
|
-
throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
|
|
4181
|
+
else if (!((_a = group.memberIds) === null || _a === void 0 ? void 0 : _a.includes(userIdOnTheSource))) {
|
|
4182
|
+
const { group: updatedGroup } = await addUserToGroup(userId, group._id, session);
|
|
4183
|
+
if (updatedGroup) {
|
|
4184
|
+
addedGroups.push(updatedGroup);
|
|
2602
4185
|
}
|
|
2603
|
-
query._id = sessionId;
|
|
2604
|
-
}
|
|
2605
|
-
// Add expiration check to only return valid sessions
|
|
2606
|
-
query.expiration = { $gt: new Date() };
|
|
2607
|
-
const sessionQuery = Session.findOne(query);
|
|
2608
|
-
if (options.lean) {
|
|
2609
|
-
return (await sessionQuery.lean());
|
|
2610
4186
|
}
|
|
2611
|
-
return await sessionQuery.exec();
|
|
2612
4187
|
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
4188
|
+
const groupsQuery = Group.find({ source: 'entra', memberIds: userIdOnTheSource }, { _id: 1, idOnTheSource: 1 });
|
|
4189
|
+
if (session) {
|
|
4190
|
+
groupsQuery.session(session);
|
|
2616
4191
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
|
|
2625
|
-
if (!sessionDoc) {
|
|
2626
|
-
throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
|
|
4192
|
+
const existingGroups = (await groupsQuery.lean());
|
|
4193
|
+
for (const group of existingGroups) {
|
|
4194
|
+
if (group.idOnTheSource && !entraIdMap.has(group.idOnTheSource)) {
|
|
4195
|
+
const { group: removedGroup } = await removeUserFromGroup(userId, group._id, session);
|
|
4196
|
+
if (removedGroup) {
|
|
4197
|
+
removedGroups.push(removedGroup);
|
|
4198
|
+
}
|
|
2627
4199
|
}
|
|
2628
|
-
sessionDoc.expiration = newExpiration || new Date(Date.now() + expires);
|
|
2629
|
-
return await sessionDoc.save();
|
|
2630
4200
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
4201
|
+
const userQuery = User.findById(userId);
|
|
4202
|
+
if (session) {
|
|
4203
|
+
userQuery.session(session);
|
|
4204
|
+
}
|
|
4205
|
+
const updatedUser = await userQuery.lean();
|
|
4206
|
+
if (!updatedUser) {
|
|
4207
|
+
throw new Error(`User not found after update: ${userId}`);
|
|
2634
4208
|
}
|
|
4209
|
+
return {
|
|
4210
|
+
user: updatedUser,
|
|
4211
|
+
addedGroups,
|
|
4212
|
+
removedGroups,
|
|
4213
|
+
};
|
|
2635
4214
|
}
|
|
2636
4215
|
/**
|
|
2637
|
-
*
|
|
4216
|
+
* Calculate relevance score for a search result
|
|
4217
|
+
* @param item - The search result item
|
|
4218
|
+
* @param searchPattern - The search pattern
|
|
4219
|
+
* @returns Relevance score (0-100)
|
|
2638
4220
|
*/
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
4221
|
+
function calculateRelevanceScore(item, searchPattern) {
|
|
4222
|
+
const exactRegex = new RegExp(`^${searchPattern}$`, 'i');
|
|
4223
|
+
const startsWithPattern = searchPattern.toLowerCase();
|
|
4224
|
+
/** Get searchable text based on type */
|
|
4225
|
+
const searchableFields = item.type === PrincipalType.USER
|
|
4226
|
+
? [item.name, item.email, item.username].filter(Boolean)
|
|
4227
|
+
: [item.name, item.email, item.description].filter(Boolean);
|
|
4228
|
+
let maxScore = 0;
|
|
4229
|
+
for (const field of searchableFields) {
|
|
4230
|
+
if (!field)
|
|
4231
|
+
continue;
|
|
4232
|
+
const fieldLower = field.toLowerCase();
|
|
4233
|
+
let score = 0;
|
|
4234
|
+
/** Exact match gets highest score */
|
|
4235
|
+
if (exactRegex.test(field)) {
|
|
4236
|
+
score = 100;
|
|
2644
4237
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
4238
|
+
else if (fieldLower.startsWith(startsWithPattern)) {
|
|
4239
|
+
/** Starts with query gets high score */
|
|
4240
|
+
score = 80;
|
|
2648
4241
|
}
|
|
2649
|
-
if (
|
|
2650
|
-
query
|
|
4242
|
+
else if (fieldLower.includes(startsWithPattern)) {
|
|
4243
|
+
/** Contains query gets medium score */
|
|
4244
|
+
score = 50;
|
|
2651
4245
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
4246
|
+
else {
|
|
4247
|
+
/** Default score for regex match */
|
|
4248
|
+
score = 10;
|
|
2655
4249
|
}
|
|
2656
|
-
|
|
2657
|
-
}
|
|
2658
|
-
catch (error) {
|
|
2659
|
-
logger.error('[deleteSession] Error deleting session:', error);
|
|
2660
|
-
throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
|
|
4250
|
+
maxScore = Math.max(maxScore, score);
|
|
2661
4251
|
}
|
|
4252
|
+
return maxScore;
|
|
2662
4253
|
}
|
|
2663
4254
|
/**
|
|
2664
|
-
*
|
|
4255
|
+
* Sort principals by relevance score and type priority
|
|
4256
|
+
* @param results - Array of results with _searchScore property
|
|
4257
|
+
* @returns Sorted array
|
|
2665
4258
|
*/
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
4259
|
+
function sortPrincipalsByRelevance(results) {
|
|
4260
|
+
return results.sort((a, b) => {
|
|
4261
|
+
if (b._searchScore !== a._searchScore) {
|
|
4262
|
+
return (b._searchScore || 0) - (a._searchScore || 0);
|
|
2671
4263
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
|
|
2675
|
-
}
|
|
2676
|
-
const query = { user: userIdString };
|
|
2677
|
-
if (options.excludeCurrentSession && options.currentSessionId) {
|
|
2678
|
-
query._id = { $ne: options.currentSessionId };
|
|
2679
|
-
}
|
|
2680
|
-
const result = await Session.deleteMany(query);
|
|
2681
|
-
if (result.deletedCount && result.deletedCount > 0) {
|
|
2682
|
-
logger.debug(`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`);
|
|
4264
|
+
if (a.type !== b.type) {
|
|
4265
|
+
return a.type === PrincipalType.USER ? -1 : 1;
|
|
2683
4266
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
|
2689
|
-
}
|
|
4267
|
+
const aName = a.name || a.email || '';
|
|
4268
|
+
const bName = b.name || b.email || '';
|
|
4269
|
+
return aName.localeCompare(bName);
|
|
4270
|
+
});
|
|
2690
4271
|
}
|
|
2691
4272
|
/**
|
|
2692
|
-
*
|
|
4273
|
+
* Transform user object to TPrincipalSearchResult format
|
|
4274
|
+
* @param user - User object from database
|
|
4275
|
+
* @returns Transformed user result
|
|
2693
4276
|
*/
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
4277
|
+
function transformUserToTPrincipalSearchResult(user) {
|
|
4278
|
+
return {
|
|
4279
|
+
id: user.id,
|
|
4280
|
+
type: PrincipalType.USER,
|
|
4281
|
+
name: user.name || user.email,
|
|
4282
|
+
email: user.email,
|
|
4283
|
+
username: user.username,
|
|
4284
|
+
avatar: user.avatar,
|
|
4285
|
+
provider: user.provider,
|
|
4286
|
+
source: 'local',
|
|
4287
|
+
idOnTheSource: user.idOnTheSource || user.id,
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
/**
|
|
4291
|
+
* Transform group object to TPrincipalSearchResult format
|
|
4292
|
+
* @param group - Group object from database
|
|
4293
|
+
* @returns Transformed group result
|
|
4294
|
+
*/
|
|
4295
|
+
function transformGroupToTPrincipalSearchResult(group) {
|
|
4296
|
+
var _a, _b;
|
|
4297
|
+
return {
|
|
4298
|
+
id: (_a = group._id) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
4299
|
+
type: PrincipalType.GROUP,
|
|
4300
|
+
name: group.name,
|
|
4301
|
+
email: group.email,
|
|
4302
|
+
avatar: group.avatar,
|
|
4303
|
+
description: group.description,
|
|
4304
|
+
source: group.source || 'local',
|
|
4305
|
+
memberCount: group.memberIds ? group.memberIds.length : 0,
|
|
4306
|
+
idOnTheSource: group.idOnTheSource || ((_b = group._id) === null || _b === void 0 ? void 0 : _b.toString()),
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* Search for principals (users and groups) by pattern matching on name/email
|
|
4311
|
+
* Returns combined results in TPrincipalSearchResult format without sorting
|
|
4312
|
+
* @param searchPattern - The pattern to search for
|
|
4313
|
+
* @param limitPerType - Maximum number of results to return
|
|
4314
|
+
* @param typeFilter - Optional array of types to filter by, or null for all types
|
|
4315
|
+
* @param session - Optional MongoDB session for transactions
|
|
4316
|
+
* @returns Array of principals in TPrincipalSearchResult format
|
|
4317
|
+
*/
|
|
4318
|
+
async function searchPrincipals(searchPattern, limitPerType = 10, typeFilter = null, session) {
|
|
4319
|
+
if (!searchPattern || searchPattern.trim().length === 0) {
|
|
4320
|
+
return [];
|
|
2697
4321
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
4322
|
+
const trimmedPattern = searchPattern.trim();
|
|
4323
|
+
const promises = [];
|
|
4324
|
+
if (!typeFilter || typeFilter.includes(PrincipalType.USER)) {
|
|
4325
|
+
/** Note: searchUsers is imported from ~/models and needs to be passed in or implemented */
|
|
4326
|
+
const userFields = 'name email username avatar provider idOnTheSource';
|
|
4327
|
+
/** For now, we'll use a direct query instead of searchUsers */
|
|
4328
|
+
const User = mongoose.models.User;
|
|
4329
|
+
const regex = new RegExp(trimmedPattern, 'i');
|
|
4330
|
+
const userQuery = User.find({
|
|
4331
|
+
$or: [{ name: regex }, { email: regex }, { username: regex }],
|
|
4332
|
+
})
|
|
4333
|
+
.select(userFields)
|
|
4334
|
+
.limit(limitPerType);
|
|
4335
|
+
if (session) {
|
|
4336
|
+
userQuery.session(session);
|
|
2702
4337
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
4338
|
+
promises.push(userQuery.lean().then((users) => users.map((user) => {
|
|
4339
|
+
var _a;
|
|
4340
|
+
const userWithId = user;
|
|
4341
|
+
return transformUserToTPrincipalSearchResult({
|
|
4342
|
+
id: ((_a = userWithId._id) === null || _a === void 0 ? void 0 : _a.toString()) || '',
|
|
4343
|
+
name: userWithId.name,
|
|
4344
|
+
email: userWithId.email,
|
|
4345
|
+
username: userWithId.username,
|
|
4346
|
+
avatar: userWithId.avatar,
|
|
4347
|
+
provider: userWithId.provider,
|
|
4348
|
+
});
|
|
4349
|
+
})));
|
|
2714
4350
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
|
|
4351
|
+
else {
|
|
4352
|
+
promises.push(Promise.resolve([]));
|
|
2718
4353
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
4354
|
+
if (!typeFilter || typeFilter.includes(PrincipalType.GROUP)) {
|
|
4355
|
+
promises.push(findGroupsByNamePattern(trimmedPattern, null, limitPerType, session).then((groups) => groups.map(transformGroupToTPrincipalSearchResult)));
|
|
4356
|
+
}
|
|
4357
|
+
else {
|
|
4358
|
+
promises.push(Promise.resolve([]));
|
|
4359
|
+
}
|
|
4360
|
+
if (!typeFilter || typeFilter.includes(PrincipalType.ROLE)) {
|
|
4361
|
+
const Role = mongoose.models.Role;
|
|
4362
|
+
if (Role) {
|
|
4363
|
+
const regex = new RegExp(trimmedPattern, 'i');
|
|
4364
|
+
const roleQuery = Role.find({ name: regex }).select('name').limit(limitPerType);
|
|
4365
|
+
if (session) {
|
|
4366
|
+
roleQuery.session(session);
|
|
4367
|
+
}
|
|
4368
|
+
promises.push(roleQuery.lean().then((roles) => roles.map((role) => ({
|
|
4369
|
+
/** Role name as ID */
|
|
4370
|
+
id: role.name,
|
|
4371
|
+
type: PrincipalType.ROLE,
|
|
4372
|
+
name: role.name,
|
|
4373
|
+
source: 'local',
|
|
4374
|
+
idOnTheSource: role.name,
|
|
4375
|
+
}))));
|
|
2728
4376
|
}
|
|
2729
|
-
return await Session.countDocuments({
|
|
2730
|
-
user: userId,
|
|
2731
|
-
expiration: { $gt: new Date() },
|
|
2732
|
-
});
|
|
2733
4377
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
throw new SessionError('Failed to count active sessions', 'COUNT_SESSIONS_FAILED');
|
|
4378
|
+
else {
|
|
4379
|
+
promises.push(Promise.resolve([]));
|
|
2737
4380
|
}
|
|
4381
|
+
const results = await Promise.all(promises);
|
|
4382
|
+
const combined = results.flat();
|
|
4383
|
+
return combined;
|
|
2738
4384
|
}
|
|
2739
4385
|
return {
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
4386
|
+
findGroupById,
|
|
4387
|
+
findGroupByExternalId,
|
|
4388
|
+
findGroupsByNamePattern,
|
|
4389
|
+
findGroupsByMemberId,
|
|
4390
|
+
createGroup,
|
|
4391
|
+
upsertGroupByExternalId,
|
|
4392
|
+
addUserToGroup,
|
|
4393
|
+
removeUserFromGroup,
|
|
4394
|
+
getUserGroups,
|
|
4395
|
+
getUserPrincipals,
|
|
4396
|
+
syncUserEntraGroups,
|
|
4397
|
+
searchPrincipals,
|
|
4398
|
+
calculateRelevanceScore,
|
|
4399
|
+
sortPrincipalsByRelevance,
|
|
2748
4400
|
};
|
|
2749
4401
|
}
|
|
2750
4402
|
|
|
2751
|
-
|
|
2752
|
-
function createTokenMethods(mongoose) {
|
|
4403
|
+
function createAclEntryMethods(mongoose) {
|
|
2753
4404
|
/**
|
|
2754
|
-
*
|
|
4405
|
+
* Find ACL entries for a specific principal (user or group)
|
|
4406
|
+
* @param principalType - The type of principal ('user', 'group')
|
|
4407
|
+
* @param principalId - The ID of the principal
|
|
4408
|
+
* @param resourceType - Optional filter by resource type
|
|
4409
|
+
* @returns Array of ACL entries
|
|
2755
4410
|
*/
|
|
2756
|
-
async function
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
const newTokenData = {
|
|
2762
|
-
...tokenData,
|
|
2763
|
-
createdAt: currentTime,
|
|
2764
|
-
expiresAt,
|
|
2765
|
-
};
|
|
2766
|
-
return await Token.create(newTokenData);
|
|
2767
|
-
}
|
|
2768
|
-
catch (error) {
|
|
2769
|
-
logger.debug('An error occurred while creating token:', error);
|
|
2770
|
-
throw error;
|
|
4411
|
+
async function findEntriesByPrincipal(principalType, principalId, resourceType) {
|
|
4412
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4413
|
+
const query = { principalType, principalId };
|
|
4414
|
+
if (resourceType) {
|
|
4415
|
+
query.resourceType = resourceType;
|
|
2771
4416
|
}
|
|
4417
|
+
return await AclEntry.find(query).lean();
|
|
2772
4418
|
}
|
|
2773
4419
|
/**
|
|
2774
|
-
*
|
|
4420
|
+
* Find ACL entries for a specific resource
|
|
4421
|
+
* @param resourceType - The type of resource ('agent', 'project', 'file')
|
|
4422
|
+
* @param resourceId - The ID of the resource
|
|
4423
|
+
* @returns Array of ACL entries
|
|
2775
4424
|
*/
|
|
2776
|
-
async function
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
return await Token.findOneAndUpdate(query, updateData, { new: true });
|
|
2780
|
-
}
|
|
2781
|
-
catch (error) {
|
|
2782
|
-
logger.debug('An error occurred while updating token:', error);
|
|
2783
|
-
throw error;
|
|
2784
|
-
}
|
|
4425
|
+
async function findEntriesByResource(resourceType, resourceId) {
|
|
4426
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4427
|
+
return await AclEntry.find({ resourceType, resourceId }).lean();
|
|
2785
4428
|
}
|
|
2786
4429
|
/**
|
|
2787
|
-
*
|
|
4430
|
+
* Find all ACL entries for a set of principals (including public)
|
|
4431
|
+
* @param principalsList - List of principals, each containing { principalType, principalId }
|
|
4432
|
+
* @param resourceType - The type of resource
|
|
4433
|
+
* @param resourceId - The ID of the resource
|
|
4434
|
+
* @returns Array of matching ACL entries
|
|
2788
4435
|
*/
|
|
2789
|
-
async function
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
4436
|
+
async function findEntriesByPrincipalsAndResource(principalsList, resourceType, resourceId) {
|
|
4437
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4438
|
+
const principalsQuery = principalsList.map((p) => ({
|
|
4439
|
+
principalType: p.principalType,
|
|
4440
|
+
...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }),
|
|
4441
|
+
}));
|
|
4442
|
+
return await AclEntry.find({
|
|
4443
|
+
$or: principalsQuery,
|
|
4444
|
+
resourceType,
|
|
4445
|
+
resourceId,
|
|
4446
|
+
}).lean();
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* Check if a set of principals has a specific permission on a resource
|
|
4450
|
+
* @param principalsList - List of principals, each containing { principalType, principalId }
|
|
4451
|
+
* @param resourceType - The type of resource
|
|
4452
|
+
* @param resourceId - The ID of the resource
|
|
4453
|
+
* @param permissionBit - The permission bit to check (use PermissionBits enum)
|
|
4454
|
+
* @returns Whether any of the principals has the permission
|
|
4455
|
+
*/
|
|
4456
|
+
async function hasPermission(principalsList, resourceType, resourceId, permissionBit) {
|
|
4457
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4458
|
+
const principalsQuery = principalsList.map((p) => ({
|
|
4459
|
+
principalType: p.principalType,
|
|
4460
|
+
...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }),
|
|
4461
|
+
}));
|
|
4462
|
+
const entry = await AclEntry.findOne({
|
|
4463
|
+
$or: principalsQuery,
|
|
4464
|
+
resourceType,
|
|
4465
|
+
resourceId,
|
|
4466
|
+
permBits: { $bitsAllSet: permissionBit },
|
|
4467
|
+
}).lean();
|
|
4468
|
+
return !!entry;
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* Get the combined effective permissions for a set of principals on a resource
|
|
4472
|
+
* @param principalsList - List of principals, each containing { principalType, principalId }
|
|
4473
|
+
* @param resourceType - The type of resource
|
|
4474
|
+
* @param resourceId - The ID of the resource
|
|
4475
|
+
* @returns {Promise<number>} Effective permission bitmask
|
|
4476
|
+
*/
|
|
4477
|
+
async function getEffectivePermissions(principalsList, resourceType, resourceId) {
|
|
4478
|
+
const aclEntries = await findEntriesByPrincipalsAndResource(principalsList, resourceType, resourceId);
|
|
4479
|
+
let effectiveBits = 0;
|
|
4480
|
+
for (const entry of aclEntries) {
|
|
4481
|
+
effectiveBits |= entry.permBits;
|
|
2804
4482
|
}
|
|
4483
|
+
return effectiveBits;
|
|
2805
4484
|
}
|
|
2806
4485
|
/**
|
|
2807
|
-
*
|
|
4486
|
+
* Grant permission to a principal for a resource
|
|
4487
|
+
* @param principalType - The type of principal ('user', 'group', 'public')
|
|
4488
|
+
* @param principalId - The ID of the principal (null for 'public')
|
|
4489
|
+
* @param resourceType - The type of resource
|
|
4490
|
+
* @param resourceId - The ID of the resource
|
|
4491
|
+
* @param permBits - The permission bits to grant
|
|
4492
|
+
* @param grantedBy - The ID of the user granting the permission
|
|
4493
|
+
* @param session - Optional MongoDB session for transactions
|
|
4494
|
+
* @param roleId - Optional role ID to associate with this permission
|
|
4495
|
+
* @returns The created or updated ACL entry
|
|
2808
4496
|
*/
|
|
2809
|
-
async function
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
4497
|
+
async function grantPermission(principalType, principalId, resourceType, resourceId, permBits, grantedBy, session, roleId) {
|
|
4498
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4499
|
+
const query = {
|
|
4500
|
+
principalType,
|
|
4501
|
+
resourceType,
|
|
4502
|
+
resourceId,
|
|
4503
|
+
};
|
|
4504
|
+
if (principalType !== PrincipalType.PUBLIC) {
|
|
4505
|
+
query.principalId =
|
|
4506
|
+
typeof principalId === 'string' && principalType !== PrincipalType.ROLE
|
|
4507
|
+
? new Types.ObjectId(principalId)
|
|
4508
|
+
: principalId;
|
|
4509
|
+
if (principalType === PrincipalType.USER) {
|
|
4510
|
+
query.principalModel = PrincipalModel.USER;
|
|
2818
4511
|
}
|
|
2819
|
-
if (
|
|
2820
|
-
|
|
4512
|
+
else if (principalType === PrincipalType.GROUP) {
|
|
4513
|
+
query.principalModel = PrincipalModel.GROUP;
|
|
2821
4514
|
}
|
|
2822
|
-
if (
|
|
2823
|
-
|
|
4515
|
+
else if (principalType === PrincipalType.ROLE) {
|
|
4516
|
+
query.principalModel = PrincipalModel.ROLE;
|
|
2824
4517
|
}
|
|
2825
|
-
const token = await Token.findOne({
|
|
2826
|
-
$and: conditions,
|
|
2827
|
-
}).lean();
|
|
2828
|
-
return token;
|
|
2829
4518
|
}
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
4519
|
+
const update = {
|
|
4520
|
+
$set: {
|
|
4521
|
+
permBits,
|
|
4522
|
+
grantedBy,
|
|
4523
|
+
grantedAt: new Date(),
|
|
4524
|
+
...(roleId && { roleId }),
|
|
4525
|
+
},
|
|
4526
|
+
};
|
|
4527
|
+
const options = {
|
|
4528
|
+
upsert: true,
|
|
4529
|
+
new: true,
|
|
4530
|
+
...(session ? { session } : {}),
|
|
4531
|
+
};
|
|
4532
|
+
return await AclEntry.findOneAndUpdate(query, update, options);
|
|
4533
|
+
}
|
|
4534
|
+
/**
|
|
4535
|
+
* Revoke permissions from a principal for a resource
|
|
4536
|
+
* @param principalType - The type of principal ('user', 'group', 'public')
|
|
4537
|
+
* @param principalId - The ID of the principal (null for 'public')
|
|
4538
|
+
* @param resourceType - The type of resource
|
|
4539
|
+
* @param resourceId - The ID of the resource
|
|
4540
|
+
* @param session - Optional MongoDB session for transactions
|
|
4541
|
+
* @returns The result of the delete operation
|
|
4542
|
+
*/
|
|
4543
|
+
async function revokePermission(principalType, principalId, resourceType, resourceId, session) {
|
|
4544
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4545
|
+
const query = {
|
|
4546
|
+
principalType,
|
|
4547
|
+
resourceType,
|
|
4548
|
+
resourceId,
|
|
4549
|
+
};
|
|
4550
|
+
if (principalType !== PrincipalType.PUBLIC) {
|
|
4551
|
+
query.principalId =
|
|
4552
|
+
typeof principalId === 'string' && principalType !== PrincipalType.ROLE
|
|
4553
|
+
? new Types.ObjectId(principalId)
|
|
4554
|
+
: principalId;
|
|
4555
|
+
}
|
|
4556
|
+
const options = session ? { session } : {};
|
|
4557
|
+
return await AclEntry.deleteOne(query, options);
|
|
4558
|
+
}
|
|
4559
|
+
/**
|
|
4560
|
+
* Modify existing permission bits for a principal on a resource
|
|
4561
|
+
* @param principalType - The type of principal ('user', 'group', 'public')
|
|
4562
|
+
* @param principalId - The ID of the principal (null for 'public')
|
|
4563
|
+
* @param resourceType - The type of resource
|
|
4564
|
+
* @param resourceId - The ID of the resource
|
|
4565
|
+
* @param addBits - Permission bits to add
|
|
4566
|
+
* @param removeBits - Permission bits to remove
|
|
4567
|
+
* @param session - Optional MongoDB session for transactions
|
|
4568
|
+
* @returns The updated ACL entry
|
|
4569
|
+
*/
|
|
4570
|
+
async function modifyPermissionBits(principalType, principalId, resourceType, resourceId, addBits, removeBits, session) {
|
|
4571
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4572
|
+
const query = {
|
|
4573
|
+
principalType,
|
|
4574
|
+
resourceType,
|
|
4575
|
+
resourceId,
|
|
4576
|
+
};
|
|
4577
|
+
if (principalType !== PrincipalType.PUBLIC) {
|
|
4578
|
+
query.principalId =
|
|
4579
|
+
typeof principalId === 'string' && principalType !== PrincipalType.ROLE
|
|
4580
|
+
? new Types.ObjectId(principalId)
|
|
4581
|
+
: principalId;
|
|
4582
|
+
}
|
|
4583
|
+
const update = {};
|
|
4584
|
+
if (addBits) {
|
|
4585
|
+
update.$bit = { permBits: { or: addBits } };
|
|
2833
4586
|
}
|
|
4587
|
+
if (removeBits) {
|
|
4588
|
+
if (!update.$bit)
|
|
4589
|
+
update.$bit = {};
|
|
4590
|
+
const bitUpdate = update.$bit;
|
|
4591
|
+
bitUpdate.permBits = { ...bitUpdate.permBits, and: ~removeBits };
|
|
4592
|
+
}
|
|
4593
|
+
const options = {
|
|
4594
|
+
new: true,
|
|
4595
|
+
...(session ? { session } : {}),
|
|
4596
|
+
};
|
|
4597
|
+
return await AclEntry.findOneAndUpdate(query, update, options);
|
|
4598
|
+
}
|
|
4599
|
+
/**
|
|
4600
|
+
* Find all resources of a specific type that a set of principals has access to
|
|
4601
|
+
* @param principalsList - List of principals, each containing { principalType, principalId }
|
|
4602
|
+
* @param resourceType - The type of resource
|
|
4603
|
+
* @param requiredPermBit - Required permission bit (use PermissionBits enum)
|
|
4604
|
+
* @returns Array of resource IDs
|
|
4605
|
+
*/
|
|
4606
|
+
async function findAccessibleResources(principalsList, resourceType, requiredPermBit) {
|
|
4607
|
+
const AclEntry = mongoose.models.AclEntry;
|
|
4608
|
+
const principalsQuery = principalsList.map((p) => ({
|
|
4609
|
+
principalType: p.principalType,
|
|
4610
|
+
...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }),
|
|
4611
|
+
}));
|
|
4612
|
+
const entries = await AclEntry.find({
|
|
4613
|
+
$or: principalsQuery,
|
|
4614
|
+
resourceType,
|
|
4615
|
+
permBits: { $bitsAllSet: requiredPermBit },
|
|
4616
|
+
}).distinct('resourceId');
|
|
4617
|
+
return entries;
|
|
2834
4618
|
}
|
|
2835
|
-
// Return all methods
|
|
2836
4619
|
return {
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
4620
|
+
findEntriesByPrincipal,
|
|
4621
|
+
findEntriesByResource,
|
|
4622
|
+
findEntriesByPrincipalsAndResource,
|
|
4623
|
+
hasPermission,
|
|
4624
|
+
getEffectivePermissions,
|
|
4625
|
+
grantPermission,
|
|
4626
|
+
revokePermission,
|
|
4627
|
+
modifyPermissionBits,
|
|
4628
|
+
findAccessibleResources,
|
|
2841
4629
|
};
|
|
2842
4630
|
}
|
|
2843
4631
|
|
|
2844
|
-
|
|
2845
|
-
function createRoleMethods(mongoose) {
|
|
4632
|
+
function createGroupMethods(mongoose) {
|
|
2846
4633
|
/**
|
|
2847
|
-
*
|
|
2848
|
-
*
|
|
2849
|
-
*
|
|
4634
|
+
* Find a group by its ID
|
|
4635
|
+
* @param groupId - The group ID
|
|
4636
|
+
* @returns The group document or null if not found
|
|
2850
4637
|
*/
|
|
2851
|
-
async function
|
|
2852
|
-
const
|
|
2853
|
-
|
|
2854
|
-
let role = await Role.findOne({ name: roleName });
|
|
2855
|
-
const defaultPerms = roleDefaults[roleName].permissions;
|
|
2856
|
-
if (!role) {
|
|
2857
|
-
// Create new role if it doesn't exist.
|
|
2858
|
-
role = new Role(roleDefaults[roleName]);
|
|
2859
|
-
}
|
|
2860
|
-
else {
|
|
2861
|
-
// Ensure role.permissions is defined.
|
|
2862
|
-
role.permissions = role.permissions || {};
|
|
2863
|
-
// For each permission type in defaults, add it if missing.
|
|
2864
|
-
for (const permType of Object.keys(defaultPerms)) {
|
|
2865
|
-
if (role.permissions[permType] == null) {
|
|
2866
|
-
role.permissions[permType] = defaultPerms[permType];
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
await role.save();
|
|
2871
|
-
}
|
|
4638
|
+
async function findGroupById(groupId) {
|
|
4639
|
+
const Group = mongoose.models.Group;
|
|
4640
|
+
return await Group.findById(groupId).lean();
|
|
2872
4641
|
}
|
|
2873
4642
|
/**
|
|
2874
|
-
*
|
|
2875
|
-
*
|
|
4643
|
+
* Create a new group
|
|
4644
|
+
* @param groupData - Group data including name, source, and optional fields
|
|
4645
|
+
* @returns The created group
|
|
2876
4646
|
*/
|
|
2877
|
-
async function
|
|
2878
|
-
const
|
|
2879
|
-
return await
|
|
4647
|
+
async function createGroup(groupData) {
|
|
4648
|
+
const Group = mongoose.models.Group;
|
|
4649
|
+
return await Group.create(groupData);
|
|
2880
4650
|
}
|
|
2881
|
-
// Return all methods you want to expose
|
|
2882
|
-
return {
|
|
2883
|
-
listRoles,
|
|
2884
|
-
initializeRoles,
|
|
2885
|
-
};
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
/**
|
|
2889
|
-
* Formats a date in YYYY-MM-DD format
|
|
2890
|
-
*/
|
|
2891
|
-
const formatDate = (date) => {
|
|
2892
|
-
return date.toISOString().split('T')[0];
|
|
2893
|
-
};
|
|
2894
|
-
// Factory function that takes mongoose instance and returns the methods
|
|
2895
|
-
function createMemoryMethods(mongoose) {
|
|
2896
4651
|
/**
|
|
2897
|
-
*
|
|
2898
|
-
*
|
|
4652
|
+
* Update an existing group
|
|
4653
|
+
* @param groupId - The ID of the group to update
|
|
4654
|
+
* @param updateData - Data to update
|
|
4655
|
+
* @returns The updated group document or null if not found
|
|
2899
4656
|
*/
|
|
2900
|
-
async function
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
return { ok: false };
|
|
2904
|
-
}
|
|
2905
|
-
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2906
|
-
const existingMemory = await MemoryEntry.findOne({ userId, key });
|
|
2907
|
-
if (existingMemory) {
|
|
2908
|
-
throw new Error('Memory with this key already exists');
|
|
2909
|
-
}
|
|
2910
|
-
await MemoryEntry.create({
|
|
2911
|
-
userId,
|
|
2912
|
-
key,
|
|
2913
|
-
value,
|
|
2914
|
-
tokenCount,
|
|
2915
|
-
updated_at: new Date(),
|
|
2916
|
-
});
|
|
2917
|
-
return { ok: true };
|
|
2918
|
-
}
|
|
2919
|
-
catch (error) {
|
|
2920
|
-
throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2921
|
-
}
|
|
4657
|
+
async function updateGroup(groupId, updateData) {
|
|
4658
|
+
const Group = mongoose.models.Group;
|
|
4659
|
+
return await Group.findByIdAndUpdate(groupId, { $set: updateData }, { new: true }).lean();
|
|
2922
4660
|
}
|
|
2923
4661
|
/**
|
|
2924
|
-
*
|
|
4662
|
+
* Delete a group
|
|
4663
|
+
* @param groupId - The ID of the group to delete
|
|
4664
|
+
* @returns The result of the delete operation
|
|
2925
4665
|
*/
|
|
2926
|
-
async function
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
return { ok: false };
|
|
2930
|
-
}
|
|
2931
|
-
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2932
|
-
await MemoryEntry.findOneAndUpdate({ userId, key }, {
|
|
2933
|
-
value,
|
|
2934
|
-
tokenCount,
|
|
2935
|
-
updated_at: new Date(),
|
|
2936
|
-
}, {
|
|
2937
|
-
upsert: true,
|
|
2938
|
-
new: true,
|
|
2939
|
-
});
|
|
2940
|
-
return { ok: true };
|
|
2941
|
-
}
|
|
2942
|
-
catch (error) {
|
|
2943
|
-
throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2944
|
-
}
|
|
4666
|
+
async function deleteGroup(groupId) {
|
|
4667
|
+
const Group = mongoose.models.Group;
|
|
4668
|
+
return await Group.deleteOne({ _id: groupId });
|
|
2945
4669
|
}
|
|
2946
4670
|
/**
|
|
2947
|
-
*
|
|
4671
|
+
* Find all groups
|
|
4672
|
+
* @returns Array of all group documents
|
|
2948
4673
|
*/
|
|
2949
|
-
async function
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
const result = await MemoryEntry.findOneAndDelete({ userId, key });
|
|
2953
|
-
return { ok: !!result };
|
|
2954
|
-
}
|
|
2955
|
-
catch (error) {
|
|
2956
|
-
throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2957
|
-
}
|
|
4674
|
+
async function getAllGroups() {
|
|
4675
|
+
const Group = mongoose.models.Group;
|
|
4676
|
+
return await Group.find().lean();
|
|
2958
4677
|
}
|
|
2959
4678
|
/**
|
|
2960
|
-
*
|
|
4679
|
+
* Find groups by source
|
|
4680
|
+
* @param source - The source ('local' or 'entra')
|
|
4681
|
+
* @returns Array of group documents
|
|
2961
4682
|
*/
|
|
2962
|
-
async function
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
return (await MemoryEntry.find({ userId }).lean());
|
|
2966
|
-
}
|
|
2967
|
-
catch (error) {
|
|
2968
|
-
throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2969
|
-
}
|
|
4683
|
+
async function findGroupsBySource(source) {
|
|
4684
|
+
const Group = mongoose.models.Group;
|
|
4685
|
+
return await Group.find({ source }).lean();
|
|
2970
4686
|
}
|
|
2971
4687
|
/**
|
|
2972
|
-
*
|
|
4688
|
+
* Find a group by its external ID
|
|
4689
|
+
* @param idOnTheSource - The external ID
|
|
4690
|
+
* @param source - The source ('entra' or 'local')
|
|
4691
|
+
* @returns The group document or null if not found
|
|
2973
4692
|
*/
|
|
2974
|
-
async function
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
4693
|
+
async function findGroupByExternalId(idOnTheSource, source = 'entra') {
|
|
4694
|
+
const Group = mongoose.models.Group;
|
|
4695
|
+
return await Group.findOne({ idOnTheSource, source }).lean();
|
|
4696
|
+
}
|
|
4697
|
+
/**
|
|
4698
|
+
* Add a member to a group
|
|
4699
|
+
* @param groupId - The group ID
|
|
4700
|
+
* @param memberId - The member ID to add (idOnTheSource value)
|
|
4701
|
+
* @returns The updated group or null if not found
|
|
4702
|
+
*/
|
|
4703
|
+
async function addMemberToGroup(groupId, memberId) {
|
|
4704
|
+
const Group = mongoose.models.Group;
|
|
4705
|
+
return await Group.findByIdAndUpdate(groupId, { $addToSet: { memberIds: memberId } }, { new: true }).lean();
|
|
4706
|
+
}
|
|
4707
|
+
/**
|
|
4708
|
+
* Remove a member from a group
|
|
4709
|
+
* @param groupId - The group ID
|
|
4710
|
+
* @param memberId - The member ID to remove (idOnTheSource value)
|
|
4711
|
+
* @returns The updated group or null if not found
|
|
4712
|
+
*/
|
|
4713
|
+
async function removeMemberFromGroup(groupId, memberId) {
|
|
4714
|
+
const Group = mongoose.models.Group;
|
|
4715
|
+
return await Group.findByIdAndUpdate(groupId, { $pull: { memberIds: memberId } }, { new: true }).lean();
|
|
4716
|
+
}
|
|
4717
|
+
/**
|
|
4718
|
+
* Find all groups that contain a specific member
|
|
4719
|
+
* @param memberId - The member ID (idOnTheSource value)
|
|
4720
|
+
* @returns Array of groups containing the member
|
|
4721
|
+
*/
|
|
4722
|
+
async function findGroupsByMemberId(memberId) {
|
|
4723
|
+
const Group = mongoose.models.Group;
|
|
4724
|
+
return await Group.find({ memberIds: memberId }).lean();
|
|
3003
4725
|
}
|
|
3004
4726
|
return {
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
4727
|
+
createGroup,
|
|
4728
|
+
updateGroup,
|
|
4729
|
+
deleteGroup,
|
|
4730
|
+
getAllGroups,
|
|
4731
|
+
findGroupById,
|
|
4732
|
+
addMemberToGroup,
|
|
4733
|
+
findGroupsBySource,
|
|
4734
|
+
removeMemberFromGroup,
|
|
4735
|
+
findGroupsByMemberId,
|
|
4736
|
+
findGroupByExternalId,
|
|
3010
4737
|
};
|
|
3011
4738
|
}
|
|
3012
4739
|
|
|
@@ -3099,7 +4826,7 @@ function createShareMethods(mongoose) {
|
|
|
3099
4826
|
return result;
|
|
3100
4827
|
}
|
|
3101
4828
|
catch (error) {
|
|
3102
|
-
logger.error('[getSharedMessages] Error getting share link', {
|
|
4829
|
+
logger$1.error('[getSharedMessages] Error getting share link', {
|
|
3103
4830
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3104
4831
|
shareId,
|
|
3105
4832
|
});
|
|
@@ -3137,7 +4864,7 @@ function createShareMethods(mongoose) {
|
|
|
3137
4864
|
query['conversationId'] = { $in: conversationIds };
|
|
3138
4865
|
}
|
|
3139
4866
|
catch (searchError) {
|
|
3140
|
-
logger.error('[getSharedLinks] Meilisearch error', {
|
|
4867
|
+
logger$1.error('[getSharedLinks] Meilisearch error', {
|
|
3141
4868
|
error: searchError instanceof Error ? searchError.message : 'Unknown error',
|
|
3142
4869
|
user,
|
|
3143
4870
|
});
|
|
@@ -3173,7 +4900,7 @@ function createShareMethods(mongoose) {
|
|
|
3173
4900
|
};
|
|
3174
4901
|
}
|
|
3175
4902
|
catch (error) {
|
|
3176
|
-
logger.error('[getSharedLinks] Error getting shares', {
|
|
4903
|
+
logger$1.error('[getSharedLinks] Error getting shares', {
|
|
3177
4904
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3178
4905
|
user,
|
|
3179
4906
|
});
|
|
@@ -3193,7 +4920,7 @@ function createShareMethods(mongoose) {
|
|
|
3193
4920
|
};
|
|
3194
4921
|
}
|
|
3195
4922
|
catch (error) {
|
|
3196
|
-
logger.error('[deleteAllSharedLinks] Error deleting shared links', {
|
|
4923
|
+
logger$1.error('[deleteAllSharedLinks] Error deleting shared links', {
|
|
3197
4924
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3198
4925
|
user,
|
|
3199
4926
|
});
|
|
@@ -3218,7 +4945,7 @@ function createShareMethods(mongoose) {
|
|
|
3218
4945
|
Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
|
|
3219
4946
|
]);
|
|
3220
4947
|
if (existingShare && existingShare.isPublic) {
|
|
3221
|
-
logger.error('[createSharedLink] Share already exists', {
|
|
4948
|
+
logger$1.error('[createSharedLink] Share already exists', {
|
|
3222
4949
|
user,
|
|
3223
4950
|
conversationId,
|
|
3224
4951
|
});
|
|
@@ -3251,7 +4978,7 @@ function createShareMethods(mongoose) {
|
|
|
3251
4978
|
if (error instanceof ShareServiceError) {
|
|
3252
4979
|
throw error;
|
|
3253
4980
|
}
|
|
3254
|
-
logger.error('[createSharedLink] Error creating shared link', {
|
|
4981
|
+
logger$1.error('[createSharedLink] Error creating shared link', {
|
|
3255
4982
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3256
4983
|
user,
|
|
3257
4984
|
conversationId,
|
|
@@ -3277,7 +5004,7 @@ function createShareMethods(mongoose) {
|
|
|
3277
5004
|
return { shareId: share.shareId || null, success: true };
|
|
3278
5005
|
}
|
|
3279
5006
|
catch (error) {
|
|
3280
|
-
logger.error('[getSharedLink] Error getting shared link', {
|
|
5007
|
+
logger$1.error('[getSharedLink] Error getting shared link', {
|
|
3281
5008
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3282
5009
|
user,
|
|
3283
5010
|
conversationId,
|
|
@@ -3322,7 +5049,7 @@ function createShareMethods(mongoose) {
|
|
|
3322
5049
|
return { shareId: newShareId, conversationId: updatedShare.conversationId };
|
|
3323
5050
|
}
|
|
3324
5051
|
catch (error) {
|
|
3325
|
-
logger.error('[updateSharedLink] Error updating shared link', {
|
|
5052
|
+
logger$1.error('[updateSharedLink] Error updating shared link', {
|
|
3326
5053
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3327
5054
|
user,
|
|
3328
5055
|
shareId,
|
|
@@ -3350,7 +5077,7 @@ function createShareMethods(mongoose) {
|
|
|
3350
5077
|
};
|
|
3351
5078
|
}
|
|
3352
5079
|
catch (error) {
|
|
3353
|
-
logger.error('[deleteSharedLink] Error deleting shared link', {
|
|
5080
|
+
logger$1.error('[deleteSharedLink] Error deleting shared link', {
|
|
3354
5081
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3355
5082
|
user,
|
|
3356
5083
|
shareId,
|
|
@@ -3370,106 +5097,6 @@ function createShareMethods(mongoose) {
|
|
|
3370
5097
|
};
|
|
3371
5098
|
}
|
|
3372
5099
|
|
|
3373
|
-
// Factory function that takes mongoose instance and returns the methods
|
|
3374
|
-
function createPluginAuthMethods(mongoose) {
|
|
3375
|
-
/**
|
|
3376
|
-
* Finds a single plugin auth entry by userId and authField
|
|
3377
|
-
*/
|
|
3378
|
-
async function findOnePluginAuth({ userId, authField, }) {
|
|
3379
|
-
try {
|
|
3380
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3381
|
-
return await PluginAuth.findOne({ userId, authField }).lean();
|
|
3382
|
-
}
|
|
3383
|
-
catch (error) {
|
|
3384
|
-
throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
/**
|
|
3388
|
-
* Finds multiple plugin auth entries by userId and pluginKeys
|
|
3389
|
-
*/
|
|
3390
|
-
async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
|
|
3391
|
-
try {
|
|
3392
|
-
if (!pluginKeys || pluginKeys.length === 0) {
|
|
3393
|
-
return [];
|
|
3394
|
-
}
|
|
3395
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3396
|
-
return await PluginAuth.find({
|
|
3397
|
-
userId,
|
|
3398
|
-
pluginKey: { $in: pluginKeys },
|
|
3399
|
-
}).lean();
|
|
3400
|
-
}
|
|
3401
|
-
catch (error) {
|
|
3402
|
-
throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
/**
|
|
3406
|
-
* Updates or creates a plugin auth entry
|
|
3407
|
-
*/
|
|
3408
|
-
async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
|
|
3409
|
-
try {
|
|
3410
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3411
|
-
const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
|
|
3412
|
-
if (existingAuth) {
|
|
3413
|
-
return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
|
|
3414
|
-
}
|
|
3415
|
-
else {
|
|
3416
|
-
const newPluginAuth = await new PluginAuth({
|
|
3417
|
-
userId,
|
|
3418
|
-
authField,
|
|
3419
|
-
value,
|
|
3420
|
-
pluginKey,
|
|
3421
|
-
});
|
|
3422
|
-
await newPluginAuth.save();
|
|
3423
|
-
return newPluginAuth.toObject();
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
catch (error) {
|
|
3427
|
-
throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
/**
|
|
3431
|
-
* Deletes plugin auth entries based on provided parameters
|
|
3432
|
-
*/
|
|
3433
|
-
async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
|
|
3434
|
-
try {
|
|
3435
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3436
|
-
if (all) {
|
|
3437
|
-
const filter = { userId };
|
|
3438
|
-
if (pluginKey) {
|
|
3439
|
-
filter.pluginKey = pluginKey;
|
|
3440
|
-
}
|
|
3441
|
-
return await PluginAuth.deleteMany(filter);
|
|
3442
|
-
}
|
|
3443
|
-
if (!authField) {
|
|
3444
|
-
throw new Error('authField is required when all is false');
|
|
3445
|
-
}
|
|
3446
|
-
return await PluginAuth.deleteOne({ userId, authField });
|
|
3447
|
-
}
|
|
3448
|
-
catch (error) {
|
|
3449
|
-
throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3450
|
-
}
|
|
3451
|
-
}
|
|
3452
|
-
/**
|
|
3453
|
-
* Deletes all plugin auth entries for a user
|
|
3454
|
-
*/
|
|
3455
|
-
async function deleteAllUserPluginAuths(userId) {
|
|
3456
|
-
try {
|
|
3457
|
-
const PluginAuth = mongoose.models.PluginAuth;
|
|
3458
|
-
return await PluginAuth.deleteMany({ userId });
|
|
3459
|
-
}
|
|
3460
|
-
catch (error) {
|
|
3461
|
-
throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
return {
|
|
3465
|
-
findOnePluginAuth,
|
|
3466
|
-
findPluginAuthsByKeys,
|
|
3467
|
-
updatePluginAuth,
|
|
3468
|
-
deletePluginAuth,
|
|
3469
|
-
deleteAllUserPluginAuths,
|
|
3470
|
-
};
|
|
3471
|
-
}
|
|
3472
|
-
|
|
3473
5100
|
/**
|
|
3474
5101
|
* Creates all database methods for all collections
|
|
3475
5102
|
*/
|
|
@@ -3480,10 +5107,15 @@ function createMethods(mongoose) {
|
|
|
3480
5107
|
...createTokenMethods(mongoose),
|
|
3481
5108
|
...createRoleMethods(mongoose),
|
|
3482
5109
|
...createMemoryMethods(mongoose),
|
|
5110
|
+
...createAgentCategoryMethods(mongoose),
|
|
5111
|
+
...createAccessRoleMethods(mongoose),
|
|
5112
|
+
...createUserGroupMethods(mongoose),
|
|
5113
|
+
...createAclEntryMethods(mongoose),
|
|
5114
|
+
...createGroupMethods(mongoose),
|
|
3483
5115
|
...createShareMethods(mongoose),
|
|
3484
5116
|
...createPluginAuthMethods(mongoose),
|
|
3485
5117
|
};
|
|
3486
5118
|
}
|
|
3487
5119
|
|
|
3488
|
-
export { Action as actionSchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, hashToken, keySchema, logger, logger
|
|
5120
|
+
export { RoleBits, Action as actionSchema, agentCategorySchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, getTransactionSupport, groupSchema, hashToken, keySchema, logger$1 as logger, logger as meiliLogger, MemoryEntrySchema as memorySchema, messageSchema, pluginAuthSchema, presetSchema, projectSchema, promptGroupSchema, promptSchema, roleSchema, sessionSchema, shareSchema, signPayload, supportsTransactions, tokenSchema, toolCallSchema, transactionSchema, userSchema };
|
|
3489
5121
|
//# sourceMappingURL=index.es.js.map
|