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