| const { |
| CacheKeys, |
| SystemRoles, |
| roleDefaults, |
| permissionsSchema, |
| removeNullishValues, |
| } = require('librechat-data-provider'); |
| const { logger } = require('@librechat/data-schemas'); |
| const getLogStores = require('~/cache/getLogStores'); |
| const { Role } = require('~/db/models'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const getRoleByName = async function (roleName, fieldsToSelect = null) { |
| const cache = getLogStores(CacheKeys.ROLES); |
| try { |
| const cachedRole = await cache.get(roleName); |
| if (cachedRole) { |
| return cachedRole; |
| } |
| let query = Role.findOne({ name: roleName }); |
| if (fieldsToSelect) { |
| query = query.select(fieldsToSelect); |
| } |
| let role = await query.lean().exec(); |
|
|
| if (!role && SystemRoles[roleName]) { |
| role = await new Role(roleDefaults[roleName]).save(); |
| await cache.set(roleName, role); |
| return role.toObject(); |
| } |
| await cache.set(roleName, role); |
| return role; |
| } catch (error) { |
| throw new Error(`Failed to retrieve or create role: ${error.message}`); |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| const updateRoleByName = async function (roleName, updates) { |
| const cache = getLogStores(CacheKeys.ROLES); |
| try { |
| const role = await Role.findOneAndUpdate( |
| { name: roleName }, |
| { $set: updates }, |
| { new: true, lean: true }, |
| ) |
| .select('-__v') |
| .lean() |
| .exec(); |
| await cache.set(roleName, role); |
| return role; |
| } catch (error) { |
| throw new Error(`Failed to update role: ${error.message}`); |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| async function updateAccessPermissions(roleName, permissionsUpdate, roleData) { |
| |
| const updates = {}; |
| for (const [permissionType, permissions] of Object.entries(permissionsUpdate)) { |
| if (permissionsSchema.shape && permissionsSchema.shape[permissionType]) { |
| updates[permissionType] = removeNullishValues(permissions); |
| } |
| } |
| if (!Object.keys(updates).length) { |
| return; |
| } |
|
|
| try { |
| const role = roleData ?? (await getRoleByName(roleName)); |
| if (!role) { |
| return; |
| } |
|
|
| const currentPermissions = role.permissions || {}; |
| const updatedPermissions = { ...currentPermissions }; |
| let hasChanges = false; |
|
|
| const unsetFields = {}; |
| const permissionTypes = Object.keys(permissionsSchema.shape || {}); |
| for (const permType of permissionTypes) { |
| if (role[permType] && typeof role[permType] === 'object') { |
| logger.info( |
| `Migrating '${roleName}' role from old schema: found '${permType}' at top level`, |
| ); |
|
|
| updatedPermissions[permType] = { |
| ...updatedPermissions[permType], |
| ...role[permType], |
| }; |
|
|
| unsetFields[permType] = 1; |
| hasChanges = true; |
| } |
| } |
|
|
| for (const [permissionType, permissions] of Object.entries(updates)) { |
| const currentTypePermissions = currentPermissions[permissionType] || {}; |
| updatedPermissions[permissionType] = { ...currentTypePermissions }; |
|
|
| for (const [permission, value] of Object.entries(permissions)) { |
| if (currentTypePermissions[permission] !== value) { |
| updatedPermissions[permissionType][permission] = value; |
| hasChanges = true; |
| logger.info( |
| `Updating '${roleName}' role permission '${permissionType}' '${permission}' from ${currentTypePermissions[permission]} to: ${value}`, |
| ); |
| } |
| } |
| } |
|
|
| if (hasChanges) { |
| const updateObj = { permissions: updatedPermissions }; |
|
|
| if (Object.keys(unsetFields).length > 0) { |
| logger.info( |
| `Unsetting old schema fields for '${roleName}' role: ${Object.keys(unsetFields).join(', ')}`, |
| ); |
|
|
| try { |
| await Role.updateOne( |
| { name: roleName }, |
| { |
| $set: updateObj, |
| $unset: unsetFields, |
| }, |
| ); |
|
|
| const cache = getLogStores(CacheKeys.ROLES); |
| const updatedRole = await Role.findOne({ name: roleName }).select('-__v').lean().exec(); |
| await cache.set(roleName, updatedRole); |
|
|
| logger.info(`Updated role '${roleName}' and removed old schema fields`); |
| } catch (updateError) { |
| logger.error(`Error during role migration update: ${updateError.message}`); |
| throw updateError; |
| } |
| } else { |
| |
| await updateRoleByName(roleName, updateObj); |
| } |
|
|
| logger.info(`Updated '${roleName}' role permissions`); |
| } else { |
| logger.info(`No changes needed for '${roleName}' role permissions`); |
| } |
| } catch (error) { |
| logger.error(`Failed to update ${roleName} role permissions:`, error); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| const migrateRoleSchema = async function (roleName) { |
| try { |
| |
| let roles; |
| if (roleName) { |
| const role = await Role.findOne({ name: roleName }); |
| roles = role ? [role] : []; |
| } else { |
| roles = await Role.find({}); |
| } |
|
|
| logger.info(`Migrating ${roles.length} roles to new schema structure`); |
| let migratedCount = 0; |
|
|
| for (const role of roles) { |
| const permissionTypes = Object.keys(permissionsSchema.shape || {}); |
| const unsetFields = {}; |
| let hasOldSchema = false; |
|
|
| |
| for (const permType of permissionTypes) { |
| if (role[permType] && typeof role[permType] === 'object') { |
| hasOldSchema = true; |
|
|
| |
| role.permissions = role.permissions || {}; |
|
|
| |
| role.permissions[permType] = { |
| ...role.permissions[permType], |
| ...role[permType], |
| }; |
|
|
| |
| unsetFields[permType] = 1; |
| } |
| } |
|
|
| if (hasOldSchema) { |
| try { |
| logger.info(`Migrating role '${role.name}' from old schema structure`); |
|
|
| |
| await Role.updateOne( |
| { _id: role._id }, |
| { |
| $set: { permissions: role.permissions }, |
| $unset: unsetFields, |
| }, |
| ); |
|
|
| |
| const cache = getLogStores(CacheKeys.ROLES); |
| const updatedRole = await Role.findById(role._id).lean().exec(); |
| await cache.set(role.name, updatedRole); |
|
|
| migratedCount++; |
| logger.info(`Migrated role '${role.name}'`); |
| } catch (error) { |
| logger.error(`Failed to migrate role '${role.name}': ${error.message}`); |
| } |
| } |
| } |
|
|
| logger.info(`Migration complete: ${migratedCount} roles migrated`); |
| return migratedCount; |
| } catch (error) { |
| logger.error(`Role schema migration failed: ${error.message}`); |
| throw error; |
| } |
| }; |
|
|
| module.exports = { |
| getRoleByName, |
| updateRoleByName, |
| migrateRoleSchema, |
| updateAccessPermissions, |
| }; |
|
|