feat: Enhance Group and User Schemas with OpenID Support and Documentation

This commit is contained in:
Ruben Talstra 2025-02-22 11:00:02 +01:00
parent 0cdccb617c
commit 82a1f554b5
No known key found for this signature in database
GPG key ID: 2A5A7174A60F3BEA
3 changed files with 56 additions and 19 deletions

View file

@ -1,6 +1,16 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const groupSchema = new mongoose.Schema( /**
* @typedef {Object} MongoGroup
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} name - The group's name
* @property {string} [description] - A brief description of the group
* @property {string} [externalId] - External identifier for the group (required for non-local groups)
* @property {string} provider - The provider of the group. Defaults to 'local'. For external groups (e.g., 'openid') the externalId is required.
* @property {Date} [createdAt] - Date when the group was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the group was last updated (added by timestamps)
*/
const groupSchema = mongoose.Schema(
{ {
name: { name: {
type: String, type: String,
@ -10,19 +20,21 @@ const groupSchema = new mongoose.Schema(
description: { description: {
type: String, type: String,
}, },
allowedEndpoints: [ externalId: {
{ type: String,
type: String, unique: true,
required: true, required: function () {
return this.provider !== 'local';
}, },
], },
allowedModels: [ provider: {
{ type: String,
type: String, required: true,
}, default: 'local',
], enum: ['local', 'openid'],
},
}, },
{ timestamps: true }, { timestamps: true },
); );

View file

@ -27,6 +27,7 @@ const { SystemRoles } = require('librechat-data-provider');
* @property {Array} [plugins=[]] - List of plugins used by the user * @property {Array} [plugins=[]] - List of plugins used by the user
* @property {Array.<MongoSession>} [refreshToken] - List of sessions with refresh tokens * @property {Array.<MongoSession>} [refreshToken] - List of sessions with refresh tokens
* @property {Date} [expiresAt] - Optional expiration date of the file * @property {Date} [expiresAt] - Optional expiration date of the file
* @property {Array.<ObjectId>} [groups] - List of group IDs the user belongs to (references to the Group model)
* @property {Date} [createdAt] - Date when the user was created (added by timestamps) * @property {Date} [createdAt] - Date when the user was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps) * @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps)
*/ */
@ -143,12 +144,11 @@ const userSchema = mongoose.Schema(
type: Boolean, type: Boolean,
default: false, default: false,
}, },
groups: [ groups: {
{ type: [mongoose.Schema.Types.ObjectId],
type: mongoose.Schema.Types.ObjectId, ref: 'Group',
ref: 'Group', default: [],
}, },
],
}, },
{ timestamps: true }, { timestamps: true },

View file

@ -8,6 +8,7 @@ const { findUser, createUser, updateUser } = require('~/models/userMethods');
const { hashToken } = require('~/server/utils/crypto'); const { hashToken } = require('~/server/utils/crypto');
const { isEnabled } = require('~/server/utils'); const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');
const Group = require('~/models/group');
let crypto; let crypto;
try { try {
@ -146,7 +147,7 @@ async function setupOpenId() {
async (tokenset, userinfo, done) => { async (tokenset, userinfo, done) => {
try { try {
logger.info(`[openidStrategy] verify login openidId: ${userinfo.sub}`); logger.info(`[openidStrategy] verify login openidId: ${userinfo.sub}`);
logger.debug('[openidStrategy] very login tokenset and userinfo', { tokenset, userinfo }); logger.debug('[openidStrategy] verify login tokenset and userinfo', { tokenset, userinfo });
let user = await findUser({ openidId: userinfo.sub }); let user = await findUser({ openidId: userinfo.sub });
logger.info( logger.info(
@ -192,6 +193,30 @@ async function setupOpenId() {
message: `You must have the "${requiredRole}" role to log in.`, message: `You must have the "${requiredRole}" role to log in.`,
}); });
} }
// Synchronize the user's groups for OpenID.
const userGroupIds = user.groups.map(id => id.toString());
// Remove existing OpenID group references.
const currentOpenIdGroups = await Group.find({
_id: { $in: userGroupIds },
provider: 'openid',
});
const currentOpenIdGroupIds = new Set(currentOpenIdGroups.map(g => g._id.toString()));
user.groups = user.groups.filter(id => !currentOpenIdGroupIds.has(id.toString()));
// Look up groups matching the roles.
const matchingGroups = await Group.find({
provider: 'openid',
externalId: { $in: roles },
});
const userGroupSet = new Set(user.groups.map(id => id.toString()));
for (const group of matchingGroups) {
const groupIdStr = group._id.toString();
if (!userGroupSet.has(groupIdStr)) {
user.groups.push(group._id);
userGroupSet.add(groupIdStr);
}
}
} }
let username = ''; let username = '';