| import { Types } from 'mongoose'; |
| import { PrincipalType, PrincipalModel } from 'librechat-data-provider'; |
| import type { Model, DeleteResult, ClientSession } from 'mongoose'; |
| import type { IAclEntry } from '~/types'; |
|
|
| export function createAclEntryMethods(mongoose: typeof import('mongoose')) { |
| |
| |
| |
| |
| |
| |
| |
| async function findEntriesByPrincipal( |
| principalType: string, |
| principalId: string | Types.ObjectId, |
| resourceType?: string, |
| ): Promise<IAclEntry[]> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const query: Record<string, unknown> = { principalType, principalId }; |
| if (resourceType) { |
| query.resourceType = resourceType; |
| } |
| return await AclEntry.find(query).lean(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function findEntriesByResource( |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| ): Promise<IAclEntry[]> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| return await AclEntry.find({ resourceType, resourceId }).lean(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function findEntriesByPrincipalsAndResource( |
| principalsList: Array<{ principalType: string; principalId?: string | Types.ObjectId }>, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| ): Promise<IAclEntry[]> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const principalsQuery = principalsList.map((p) => ({ |
| principalType: p.principalType, |
| ...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }), |
| })); |
|
|
| return await AclEntry.find({ |
| $or: principalsQuery, |
| resourceType, |
| resourceId, |
| }).lean(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function hasPermission( |
| principalsList: Array<{ principalType: string; principalId?: string | Types.ObjectId }>, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| permissionBit: number, |
| ): Promise<boolean> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const principalsQuery = principalsList.map((p) => ({ |
| principalType: p.principalType, |
| ...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }), |
| })); |
|
|
| const entry = await AclEntry.findOne({ |
| $or: principalsQuery, |
| resourceType, |
| resourceId, |
| permBits: { $bitsAllSet: permissionBit }, |
| }).lean(); |
|
|
| return !!entry; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function getEffectivePermissions( |
| principalsList: Array<{ principalType: string; principalId?: string | Types.ObjectId }>, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| ): Promise<number> { |
| const aclEntries = await findEntriesByPrincipalsAndResource( |
| principalsList, |
| resourceType, |
| resourceId, |
| ); |
|
|
| let effectiveBits = 0; |
| for (const entry of aclEntries) { |
| effectiveBits |= entry.permBits; |
| } |
| return effectiveBits; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function grantPermission( |
| principalType: string, |
| principalId: string | Types.ObjectId | null, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| permBits: number, |
| grantedBy: string | Types.ObjectId, |
| session?: ClientSession, |
| roleId?: string | Types.ObjectId, |
| ): Promise<IAclEntry | null> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const query: Record<string, unknown> = { |
| principalType, |
| resourceType, |
| resourceId, |
| }; |
|
|
| if (principalType !== PrincipalType.PUBLIC) { |
| query.principalId = |
| typeof principalId === 'string' && principalType !== PrincipalType.ROLE |
| ? new Types.ObjectId(principalId) |
| : principalId; |
| if (principalType === PrincipalType.USER) { |
| query.principalModel = PrincipalModel.USER; |
| } else if (principalType === PrincipalType.GROUP) { |
| query.principalModel = PrincipalModel.GROUP; |
| } else if (principalType === PrincipalType.ROLE) { |
| query.principalModel = PrincipalModel.ROLE; |
| } |
| } |
|
|
| const update = { |
| $set: { |
| permBits, |
| grantedBy, |
| grantedAt: new Date(), |
| ...(roleId && { roleId }), |
| }, |
| }; |
|
|
| const options = { |
| upsert: true, |
| new: true, |
| ...(session ? { session } : {}), |
| }; |
|
|
| return await AclEntry.findOneAndUpdate(query, update, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function revokePermission( |
| principalType: string, |
| principalId: string | Types.ObjectId | null, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| session?: ClientSession, |
| ): Promise<DeleteResult> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const query: Record<string, unknown> = { |
| principalType, |
| resourceType, |
| resourceId, |
| }; |
|
|
| if (principalType !== PrincipalType.PUBLIC) { |
| query.principalId = |
| typeof principalId === 'string' && principalType !== PrincipalType.ROLE |
| ? new Types.ObjectId(principalId) |
| : principalId; |
| } |
|
|
| const options = session ? { session } : {}; |
|
|
| return await AclEntry.deleteOne(query, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function modifyPermissionBits( |
| principalType: string, |
| principalId: string | Types.ObjectId | null, |
| resourceType: string, |
| resourceId: string | Types.ObjectId, |
| addBits?: number | null, |
| removeBits?: number | null, |
| session?: ClientSession, |
| ): Promise<IAclEntry | null> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const query: Record<string, unknown> = { |
| principalType, |
| resourceType, |
| resourceId, |
| }; |
|
|
| if (principalType !== PrincipalType.PUBLIC) { |
| query.principalId = |
| typeof principalId === 'string' && principalType !== PrincipalType.ROLE |
| ? new Types.ObjectId(principalId) |
| : principalId; |
| } |
|
|
| const update: Record<string, unknown> = {}; |
|
|
| if (addBits) { |
| update.$bit = { permBits: { or: addBits } }; |
| } |
|
|
| if (removeBits) { |
| if (!update.$bit) update.$bit = {}; |
| const bitUpdate = update.$bit as Record<string, unknown>; |
| bitUpdate.permBits = { ...(bitUpdate.permBits as Record<string, unknown>), and: ~removeBits }; |
| } |
|
|
| const options = { |
| new: true, |
| ...(session ? { session } : {}), |
| }; |
|
|
| return await AclEntry.findOneAndUpdate(query, update, options); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function findAccessibleResources( |
| principalsList: Array<{ principalType: string; principalId?: string | Types.ObjectId }>, |
| resourceType: string, |
| requiredPermBit: number, |
| ): Promise<Types.ObjectId[]> { |
| const AclEntry = mongoose.models.AclEntry as Model<IAclEntry>; |
| const principalsQuery = principalsList.map((p) => ({ |
| principalType: p.principalType, |
| ...(p.principalType !== PrincipalType.PUBLIC && { principalId: p.principalId }), |
| })); |
|
|
| const entries = await AclEntry.find({ |
| $or: principalsQuery, |
| resourceType, |
| permBits: { $bitsAllSet: requiredPermBit }, |
| }).distinct('resourceId'); |
|
|
| return entries; |
| } |
|
|
| return { |
| findEntriesByPrincipal, |
| findEntriesByResource, |
| findEntriesByPrincipalsAndResource, |
| hasPermission, |
| getEffectivePermissions, |
| grantPermission, |
| revokePermission, |
| modifyPermissionBits, |
| findAccessibleResources, |
| }; |
| } |
|
|
| export type AclEntryMethods = ReturnType<typeof createAclEntryMethods>; |
|
|