consider migration and update user

This commit is contained in:
Artyom Bogachenko 2026-03-30 20:36:16 +03:00
parent f51fe917d1
commit b98fd221b9
4 changed files with 78 additions and 6 deletions

View file

@ -30,18 +30,21 @@ const {
GetModelController,
} = require('~/server/controllers/agents/openai');
const { getEffectivePermissions } = require('~/server/services/PermissionService');
const { validateAgentApiKey, findUser } = require('~/models');
const { configMiddleware } = require('~/server/middleware');
const { getAppConfig } = require('~/server/services/Config');
const db = require('~/models');
const router = express.Router();
const apiKeyMiddleware = createRequireApiKeyAuth({ validateAgentApiKey, findUser });
const apiKeyMiddleware = createRequireApiKeyAuth({
validateAgentApiKey: db.validateAgentApiKey,
findUser: db.findUser
});
const requireRemoteAgentAuth = createRemoteAgentAuth({
apiKeyMiddleware,
findUser: db.findUser,
updateUser: db.updateUser,
getAppConfig,
});

View file

@ -33,18 +33,21 @@ const {
listModels,
} = require('~/server/controllers/agents/responses');
const { getEffectivePermissions } = require('~/server/services/PermissionService');
const { validateAgentApiKey, findUser } = require('~/models');
const { configMiddleware } = require('~/server/middleware');
const { getAppConfig } = require('~/server/services/Config');
const db = require('~/models');
const router = express.Router();
const apiKeyMiddleware = createRequireApiKeyAuth({ validateAgentApiKey, findUser });
const apiKeyMiddleware = createRequireApiKeyAuth({
validateAgentApiKey: db.validateAgentApiKey,
findUser: db.findUser
});
const requireRemoteAgentAuth = createRemoteAgentAuth({
apiKeyMiddleware,
findUser: db.findUser,
updateUser: db.updateUser,
getAppConfig,
});

View file

@ -84,6 +84,7 @@ function makeConfig(oidcOverrides?: object, apiKeyOverrides?: object): AppConfig
function makeDeps(appConfig: AppConfig | null = makeConfig()) {
return {
findUser: jest.fn(),
updateUser: jest.fn(),
getAppConfig: jest.fn().mockResolvedValue(appConfig),
apiKeyMiddleware: jest.fn((_req: unknown, _res: unknown, next: () => void) => next()),
};
@ -454,4 +455,48 @@ describe('createRemoteAgentAuth', () => {
expect(await captureEmailArg({ sub: 's3', upn: 'upn@corp.com' })).toBe('upn@corp.com');
});
});
describe('update user and migration scenarios', () => {
it('persists openidId binding when migration is needed', async () => {
const mockUpdateUser = jest.fn().mockResolvedValue(undefined);
setupOidcMocks({ sub: 'sub-new', email: 'existing@test.com' });
(findOpenIDUser as jest.Mock).mockResolvedValue({
user: { ...mockUser, openidId: undefined, role: 'user' },
error: null,
migration: true,
});
const deps = { ...makeDeps(), updateUser: mockUpdateUser };
await createRemoteAgentAuth(deps as any)(
makeReq({ authorization: `Bearer ${FAKE_TOKEN}` }) as Request,
makeRes().res,
mockNext,
);
expect(mockUpdateUser).toHaveBeenCalledWith(
mockUser.id,
expect.objectContaining({ provider: 'openid', openidId: 'sub-new' }),
);
expect(mockNext).toHaveBeenCalled();
});
it('does not call updateUser when migration is false and role exists', async () => {
const mockUpdateUser = jest.fn();
setupOidcMocks({ sub: 'sub123', email: 'agent@test.com' });
(findOpenIDUser as jest.Mock).mockResolvedValue({
user: { ...mockUser, role: 'user' },
error: null,
migration: false,
});
const deps = { ...makeDeps(), updateUser: mockUpdateUser };
await createRemoteAgentAuth(deps as any)(
makeReq({ authorization: `Bearer ${FAKE_TOKEN}` }) as Request,
makeRes().res,
mockNext,
);
expect(mockUpdateUser).not.toHaveBeenCalled();
});
})
});

View file

@ -6,12 +6,14 @@ import type { RequestHandler, Request, Response, NextFunction } from 'express';
import type { JwtPayload } from 'jsonwebtoken';
import type { AppConfig, IUser, UserMethods } from '@librechat/data-schemas';
import type { TAgentsEndpoint } from 'librechat-data-provider';
import { SystemRoles } from 'librechat-data-provider';
import { isEnabled, math } from '~/utils';
import { findOpenIDUser } from '../auth/openid';
export interface RemoteAgentAuthDeps {
apiKeyMiddleware: RequestHandler;
findUser: UserMethods['findUser'];
updateUser: UserMethods['updateUser'];
getAppConfig: () => Promise<AppConfig | null>;
}
@ -141,8 +143,9 @@ async function resolveUser(
token: string,
payload: JwtPayload,
findUser: UserMethods['findUser'],
updateUser: UserMethods['updateUser'],
): Promise<IUser | null> {
const { user, error } = await findOpenIDUser({
const { user, error, migration } = await findOpenIDUser({
findUser,
email: getEmail(payload),
openidId: payload.sub ?? '',
@ -153,6 +156,23 @@ async function resolveUser(
if (error != null || user == null) return null;
user.id = String(user._id);
const updateData: Partial<IUser> = {};
if (migration) {
updateData.provider = 'openid';
updateData.openidId = payload.sub;
}
if (!user.role) {
user.role = SystemRoles.USER;
updateData.role = SystemRoles.USER;
}
if (Object.keys(updateData).length > 0) {
await updateUser(user.id, updateData);
}
user.federatedTokens = { access_token: token, expires_at: payload.exp };
return user;
}
@ -180,6 +200,7 @@ async function resolveUser(
export function createRemoteAgentAuth({
apiKeyMiddleware,
findUser,
updateUser,
getAppConfig,
}: RemoteAgentAuthDeps): RequestHandler {
return async (req: Request, res: Response, next: NextFunction) => {
@ -202,7 +223,7 @@ export function createRemoteAgentAuth({
try {
const payload = await verifyOidcBearer(token, authConfig.oidc);
const user = await resolveUser(token, payload, findUser);
const user = await resolveUser(token, payload, findUser, updateUser);
if (user == null) {
logger.warn('[remoteAgentAuth] OIDC token valid but no matching LibreChat user');