| | 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>; |
| |
|