diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index c5d5c5b888..7bb21a7c58 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -6,7 +6,13 @@ const { getAppConfig } = require('~/server/services/Config'); const getAvailablePluginsController = async (req, res) => { try { - const appConfig = await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId }); + const appConfig = + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); const { filteredTools = [], includedTools = [] } = appConfig; const uniquePlugins = filterUniquePlugins(availableTools); @@ -41,7 +47,12 @@ const getAvailableTools = async (req, res) => { } const appConfig = - req.config ?? (await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId })); + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); let toolDefinitions = await getCachedTools(); diff --git a/api/server/controllers/PluginController.spec.js b/api/server/controllers/PluginController.spec.js index 9288680567..b392ab575d 100644 --- a/api/server/controllers/PluginController.spec.js +++ b/api/server/controllers/PluginController.spec.js @@ -98,10 +98,10 @@ describe('PluginController', () => { require('~/app/clients/tools').availableTools.push(...mockPlugins); - getAppConfig.mockResolvedValueOnce({ + mockReq.config = { filteredTools: [], includedTools: ['key1'], - }); + }; await getAvailablePluginsController(mockReq, mockRes); @@ -118,10 +118,10 @@ describe('PluginController', () => { require('~/app/clients/tools').availableTools.push(...mockPlugins); - getAppConfig.mockResolvedValueOnce({ + mockReq.config = { filteredTools: ['key2'], includedTools: [], - }); + }; await getAvailablePluginsController(mockReq, mockRes); @@ -139,10 +139,10 @@ describe('PluginController', () => { require('~/app/clients/tools').availableTools.push(...mockPlugins); - getAppConfig.mockResolvedValueOnce({ + mockReq.config = { includedTools: ['key1', 'key2'], filteredTools: ['key2'], - }); + }; await getAvailablePluginsController(mockReq, mockRes); diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 5b38d6d562..340ebdbb4c 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -26,7 +26,13 @@ const { getLogStores } = require('~/cache'); const db = require('~/models'); const getUserController = async (req, res) => { - const appConfig = await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId }); + const appConfig = + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); /** @type {IUser} */ const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user }; /** @@ -165,7 +171,13 @@ const deleteUserMcpServers = async (userId) => { }; const updateUserPluginsController = async (req, res) => { - const appConfig = await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId }); + const appConfig = + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); const { user } = req; const { pluginKey, action, auth, isEntityTool } = req.body; try { diff --git a/api/server/middleware/checkDomainAllowed.js b/api/server/middleware/checkDomainAllowed.js index f7a3f00e68..104e6af792 100644 --- a/api/server/middleware/checkDomainAllowed.js +++ b/api/server/middleware/checkDomainAllowed.js @@ -18,6 +18,7 @@ const checkDomainAllowed = async (req, res, next) => { const email = req?.user?.email; const appConfig = await getAppConfig({ role: req?.user?.role, + userId: req?.user?.id, tenantId: req?.user?.tenantId, }); diff --git a/api/server/services/Config/loadConfigModels.spec.js b/api/server/services/Config/loadConfigModels.spec.js index d3ec0309ae..59f9a5c04e 100644 --- a/api/server/services/Config/loadConfigModels.spec.js +++ b/api/server/services/Config/loadConfigModels.spec.js @@ -93,6 +93,41 @@ describe('loadConfigModels', () => { expect(result).toEqual({}); }); + it('passes userId when resolving scoped model config', async () => { + getAppConfig.mockResolvedValue({}); + + await loadConfigModels({ + user: { id: 'testUserId', role: 'USER', tenantId: 'tenant-a' }, + }); + + expect(getAppConfig).toHaveBeenCalledWith({ + role: 'USER', + userId: 'testUserId', + tenantId: 'tenant-a', + }); + }); + + it('uses req.config when available instead of calling getAppConfig', async () => { + const result = await loadConfigModels({ + user: { id: 'testUserId' }, + config: { + endpoints: { + custom: [ + { + name: 'LocalOnly', + apiKey: 'local-key', + baseURL: 'https://example.com/v1', + models: { default: ['local-model'], fetch: false }, + }, + ], + }, + }, + }); + + expect(getAppConfig).not.toHaveBeenCalled(); + expect(result.LocalOnly).toEqual(['local-model']); + }); + it('handles azure models and endpoint correctly', async () => { getAppConfig.mockResolvedValue({ endpoints: { diff --git a/api/server/services/Config/loadDefaultModels.js b/api/server/services/Config/loadDefaultModels.js index 85f2c42a33..cc8da0bbc0 100644 --- a/api/server/services/Config/loadDefaultModels.js +++ b/api/server/services/Config/loadDefaultModels.js @@ -17,7 +17,12 @@ const { getAppConfig } = require('./app'); async function loadDefaultModels(req) { try { const appConfig = - req.config ?? (await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId })); + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); const vertexConfig = appConfig?.endpoints?.[EModelEndpoint.anthropic]?.vertexConfig; const [openAI, anthropic, azureOpenAI, assistants, azureAssistants, google, bedrock] = diff --git a/api/server/services/Files/Audio/STTService.js b/api/server/services/Files/Audio/STTService.js index 7329bf6ac2..2caea1ffe0 100644 --- a/api/server/services/Files/Audio/STTService.js +++ b/api/server/services/Files/Audio/STTService.js @@ -142,6 +142,7 @@ class STTService { req.config ?? (await getAppConfig({ role: req?.user?.role, + userId: req?.user?.id, tenantId: req?.user?.tenantId, })); const sttSchema = appConfig?.speech?.stt; diff --git a/api/server/services/Files/Audio/TTSService.js b/api/server/services/Files/Audio/TTSService.js index 1125dd74ed..80f4239cc6 100644 --- a/api/server/services/Files/Audio/TTSService.js +++ b/api/server/services/Files/Audio/TTSService.js @@ -297,6 +297,7 @@ class TTSService { req.config ?? (await getAppConfig({ role: req.user?.role, + userId: req.user?.id, tenantId: req.user?.tenantId, })); try { @@ -366,6 +367,7 @@ class TTSService { req.config ?? (await getAppConfig({ role: req.user?.role, + userId: req.user?.id, tenantId: req.user?.tenantId, })); const provider = this.getProvider(appConfig); diff --git a/api/server/services/Files/Audio/getCustomConfigSpeech.js b/api/server/services/Files/Audio/getCustomConfigSpeech.js index b438771ec1..1edca8e188 100644 --- a/api/server/services/Files/Audio/getCustomConfigSpeech.js +++ b/api/server/services/Files/Audio/getCustomConfigSpeech.js @@ -15,10 +15,13 @@ const { getAppConfig } = require('~/server/services/Config'); */ async function getCustomConfigSpeech(req, res) { try { - const appConfig = await getAppConfig({ - role: req.user?.role, - tenantId: req.user?.tenantId, - }); + const appConfig = + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); if (!appConfig) { return res.status(200).send({ diff --git a/api/server/services/Files/Audio/getVoices.js b/api/server/services/Files/Audio/getVoices.js index 22bd7cea6e..a7cd7dbc30 100644 --- a/api/server/services/Files/Audio/getVoices.js +++ b/api/server/services/Files/Audio/getVoices.js @@ -18,6 +18,7 @@ async function getVoices(req, res) { req.config ?? (await getAppConfig({ role: req.user?.role, + userId: req.user?.id, tenantId: req.user?.tenantId, })); diff --git a/api/server/services/MCP.js b/api/server/services/MCP.js index 9d27734d94..f2e845b927 100644 --- a/api/server/services/MCP.js +++ b/api/server/services/MCP.js @@ -415,7 +415,11 @@ async function createMCPTools({ const serverConfig = config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id, configServers)); if (serverConfig?.url) { - const appConfig = await getAppConfig({ role: user?.role, tenantId: user?.tenantId }); + const appConfig = await getAppConfig({ + role: user?.role, + tenantId: user?.tenantId, + userId: user?.id, + }); const allowedDomains = appConfig?.mcpSettings?.allowedDomains; const allowedAddresses = appConfig?.mcpSettings?.allowedAddresses; const isDomainAllowed = await isMCPDomainAllowed( @@ -503,7 +507,11 @@ async function createMCPTool({ const serverConfig = config ?? (await getMCPServersRegistry().getServerConfig(serverName, user?.id, configServers)); if (serverConfig?.url) { - const appConfig = await getAppConfig({ role: user?.role, tenantId: user?.tenantId }); + const appConfig = await getAppConfig({ + role: user?.role, + tenantId: user?.tenantId, + userId: user?.id, + }); const allowedDomains = appConfig?.mcpSettings?.allowedDomains; const allowedAddresses = appConfig?.mcpSettings?.allowedAddresses; const isDomainAllowed = await isMCPDomainAllowed( diff --git a/api/server/services/MCP.spec.js b/api/server/services/MCP.spec.js index 3288aadd90..8b9f055ff8 100644 --- a/api/server/services/MCP.spec.js +++ b/api/server/services/MCP.spec.js @@ -922,8 +922,12 @@ describe('User parameter passing tests', () => { // Should not call reinitMCPServer since domain check failed expect(mockReinitMCPServer).not.toHaveBeenCalled(); - // Verify getAppConfig was called with user role - expect(mockGetAppConfig).toHaveBeenCalledWith({ role: 'user' }); + // Verify getAppConfig was called with the user scope + expect(mockGetAppConfig).toHaveBeenCalledWith({ + role: 'user', + tenantId: undefined, + userId: 'domain-test-user', + }); // Verify domain validation was called with correct parameters expect(mockIsMCPDomainAllowed).toHaveBeenCalledWith( @@ -971,8 +975,12 @@ describe('User parameter passing tests', () => { // Should create tool successfully expect(result).toBeDefined(); - // Verify getAppConfig was called with user role - expect(mockGetAppConfig).toHaveBeenCalledWith({ role: 'admin' }); + // Verify getAppConfig was called with the user scope + expect(mockGetAppConfig).toHaveBeenCalledWith({ + role: 'admin', + tenantId: undefined, + userId: 'domain-test-user', + }); }); it('should skip domain validation for stdio transports (no URL)', async () => { @@ -1047,8 +1055,12 @@ describe('User parameter passing tests', () => { // Should not call reinitMCPServer since domain check failed early expect(mockReinitMCPServer).not.toHaveBeenCalled(); - // Verify getAppConfig was called with user role - expect(mockGetAppConfig).toHaveBeenCalledWith({ role: 'user' }); + // Verify getAppConfig was called with the user scope + expect(mockGetAppConfig).toHaveBeenCalledWith({ + role: 'user', + tenantId: undefined, + userId: 'domain-test-user', + }); }); it('should use user role when fetching domain restrictions', async () => { @@ -1100,9 +1112,17 @@ describe('User parameter passing tests', () => { availableTools, }); - // Verify getAppConfig was called with correct roles - expect(mockGetAppConfig).toHaveBeenNthCalledWith(1, { role: 'admin' }); - expect(mockGetAppConfig).toHaveBeenNthCalledWith(2, { role: 'user' }); + // Verify getAppConfig was called with the correct user scopes + expect(mockGetAppConfig).toHaveBeenNthCalledWith(1, { + role: 'admin', + tenantId: undefined, + userId: 'admin-user', + }); + expect(mockGetAppConfig).toHaveBeenNthCalledWith(2, { + role: 'user', + tenantId: undefined, + userId: 'regular-user', + }); }); }); diff --git a/packages/api/src/endpoints/config/endpoints.spec.ts b/packages/api/src/endpoints/config/endpoints.spec.ts index b01b5c1c50..8d77c82960 100644 --- a/packages/api/src/endpoints/config/endpoints.spec.ts +++ b/packages/api/src/endpoints/config/endpoints.spec.ts @@ -1,17 +1,46 @@ +import { Types } from 'mongoose'; import { AgentCapabilities, EModelEndpoint, + PrincipalType, defaultAgentCapabilities, } from 'librechat-data-provider'; -import { createEndpointsConfigService } from './endpoints'; -import type { AppConfig } from '@librechat/data-schemas'; + +import type { AppConfig, IConfig } from '@librechat/data-schemas'; +import type { AppConfigServiceDeps } from '~/app/service'; import type { EndpointsConfigDeps } from './endpoints'; import type { ServerRequest } from '~/types'; +import { createEndpointsConfigService } from './endpoints'; +import { createAppConfigService } from '~/app/service'; + function appConfig(partial: Record): AppConfig { return partial as unknown as AppConfig; } +function configDoc(partial: Record): IConfig { + return partial as unknown as IConfig; +} + +function createAppConfigCache() { + const store = new Map(); + return { + get: jest.fn((key: string) => Promise.resolve(store.get(key))), + set: jest.fn((key: string, value: unknown) => { + store.set(key, value); + return Promise.resolve(undefined); + }), + delete: jest.fn((key: string) => { + store.delete(key); + return Promise.resolve(true); + }), + }; +} + +type ConfigPrincipals = NonNullable< + Parameters[0] +>; + function createMockDeps(overrides: Partial = {}): EndpointsConfigDeps { return { getAppConfig: jest.fn().mockResolvedValue(appConfig({ endpoints: {} })), @@ -23,7 +52,13 @@ function createMockDeps(overrides: Partial = {}): Endpoints }; } -function fakeReq(overrides: Partial = {}): ServerRequest { +type TestRequestOverrides = { + body?: Partial; + config?: AppConfig; + user?: { id?: string; role?: string; tenantId?: string }; +}; + +function fakeReq(overrides: TestRequestOverrides = {}): ServerRequest { return { user: { id: 'u1', role: 'USER' }, ...overrides } as ServerRequest; } @@ -172,6 +207,83 @@ describe('createEndpointsConfigService', () => { expect(mockGetAppConfig).not.toHaveBeenCalled(); }); + + it('passes userId when resolving scoped endpoint config', async () => { + const mockGetAppConfig = jest.fn().mockResolvedValue(appConfig({ endpoints: {} })); + const deps = createMockDeps({ getAppConfig: mockGetAppConfig }); + const { getEndpointsConfig } = createEndpointsConfigService(deps); + + await getEndpointsConfig( + fakeReq({ user: { id: 'u1', role: 'USER', tenantId: 'tenant-a' } }), + ); + + expect(mockGetAppConfig).toHaveBeenCalledWith({ + role: 'USER', + userId: 'u1', + tenantId: 'tenant-a', + }); + }); + + it('exposes custom endpoints from group-scoped overrides for grouped users', async () => { + const groupId = new Types.ObjectId('6a0aea2172e2d59d4658c9d2'); + const getUserPrincipals = jest.fn().mockResolvedValue([ + { principalType: PrincipalType.ROLE, principalId: 'USER' }, + { principalType: PrincipalType.USER, principalId: 'u1' }, + { principalType: PrincipalType.GROUP, principalId: groupId }, + ]); + const getApplicableConfigs = jest.fn((principals: ConfigPrincipals = []) => + Promise.resolve( + principals.some( + (principal) => + principal.principalType === PrincipalType.GROUP && + String(principal.principalId) === groupId.toString(), + ) + ? [ + configDoc({ + principalType: PrincipalType.GROUP, + principalId: groupId, + priority: 20, + isActive: true, + overrides: { + endpoints: { + custom: [ + { + name: 'FOO', + apiKey: '${FOO_KEY}', + baseURL: '${FOO_URL}', + models: { fetch: true }, + }, + ], + }, + }, + }), + ] + : [], + ), + ); + const { getAppConfig } = createAppConfigService({ + loadBaseConfig: jest.fn().mockResolvedValue(appConfig({ endpoints: {} })), + setCachedTools: jest.fn().mockResolvedValue(undefined), + getCache: jest.fn().mockReturnValue(createAppConfigCache()), + cacheKeys: { APP_CONFIG: 'app_config' }, + getApplicableConfigs, + getUserPrincipals, + }); + const { getEndpointsConfig } = createEndpointsConfigService({ + getAppConfig, + loadDefaultEndpointsConfig: jest.fn().mockResolvedValue({}), + }); + + const result = await getEndpointsConfig(fakeReq({ user: { id: 'u1', role: 'USER' } })); + + expect(getUserPrincipals).toHaveBeenCalledWith({ userId: 'u1', role: 'USER' }); + expect(getApplicableConfigs).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ principalType: PrincipalType.GROUP, principalId: groupId }), + ]), + ); + expect(result?.FOO).toEqual(expect.objectContaining({ type: EModelEndpoint.custom })); + }); }); describe('checkCapability', () => { diff --git a/packages/api/src/endpoints/config/endpoints.ts b/packages/api/src/endpoints/config/endpoints.ts index 1a6b98d195..0d500219d9 100644 --- a/packages/api/src/endpoints/config/endpoints.ts +++ b/packages/api/src/endpoints/config/endpoints.ts @@ -14,7 +14,11 @@ type DefaultEndpointsResult = Record; export interface EndpointsConfigDeps { - getAppConfig: (params: { role?: string | null; tenantId?: string }) => Promise; + getAppConfig: (params: { + role?: string; + userId?: string; + tenantId?: string; + }) => Promise; loadDefaultEndpointsConfig: (appConfig: AppConfig) => Promise; loadCustomEndpointsConfig?: (custom: unknown) => TCustomEndpointsConfig | undefined; } @@ -28,7 +32,12 @@ export function createEndpointsConfigService(deps: EndpointsConfigDeps) { async function getEndpointsConfig(req: ServerRequest): Promise { const appConfig = - req.config ?? (await getAppConfig({ role: req.user?.role, tenantId: req.user?.tenantId })); + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); const defaultEndpointsConfig = await loadDefaultEndpointsConfig(appConfig); const customEndpointsConfig = loadCustomEndpointsConfig(appConfig?.endpoints?.custom); diff --git a/packages/api/src/endpoints/config/models.ts b/packages/api/src/endpoints/config/models.ts index 2d87d5d242..22c5207b1d 100644 --- a/packages/api/src/endpoints/config/models.ts +++ b/packages/api/src/endpoints/config/models.ts @@ -22,7 +22,11 @@ interface ResolvedEndpoint { } export interface LoadConfigModelsDeps { - getAppConfig: (params: { role?: string | null; tenantId?: string }) => Promise; + getAppConfig: (params: { + role?: string; + userId?: string; + tenantId?: string; + }) => Promise; getUserKeyValues: GetUserKeyValuesFunction; fetchModels?: (params: FetchModelsParams) => Promise; } @@ -31,10 +35,13 @@ export function createLoadConfigModels(deps: LoadConfigModelsDeps) { const { getAppConfig, getUserKeyValues, fetchModels = defaultFetchModels } = deps; return async function loadConfigModels(req: ServerRequest): Promise { - const appConfig = await getAppConfig({ - role: req.user?.role, - tenantId: req.user?.tenantId, - }); + const appConfig = + req.config ?? + (await getAppConfig({ + role: req.user?.role, + userId: req.user?.id, + tenantId: req.user?.tenantId, + })); if (!appConfig) { return {}; } diff --git a/packages/api/src/middleware/balance.spec.ts b/packages/api/src/middleware/balance.spec.ts index 8badd2e106..67ab0fe7f6 100644 --- a/packages/api/src/middleware/balance.spec.ts +++ b/packages/api/src/middleware/balance.spec.ts @@ -381,6 +381,34 @@ describe('createSetBalanceConfig', () => { // lastRefill should have default value from schema expect(balanceRecord?.lastRefill).toBeInstanceOf(Date); }); + + test('should pass user identity when resolving balance config', async () => { + const userId = new mongoose.Types.ObjectId(); + const getAppConfig = jest.fn().mockResolvedValue({ + balance: { enabled: false }, + }); + + const middleware = createSetBalanceConfig({ + getAppConfig, + findBalanceByUser, + upsertBalanceFields, + }); + + const req = createMockRequest(userId); + req.user = { + ...req.user, + role: 'USER', + tenantId: 'tenant-a', + } as typeof req.user; + + await middleware(req as ServerRequest, createMockResponse() as ServerResponse, mockNext); + + expect(getAppConfig).toHaveBeenCalledWith({ + role: 'USER', + userId: userId.toString(), + tenantId: 'tenant-a', + }); + }); }); describe('Update Scenarios', () => { diff --git a/packages/api/src/middleware/balance.ts b/packages/api/src/middleware/balance.ts index 4b746820f4..061fbcbf89 100644 --- a/packages/api/src/middleware/balance.ts +++ b/packages/api/src/middleware/balance.ts @@ -14,6 +14,7 @@ import { getBalanceConfig } from '~/app/config'; export interface BalanceMiddlewareOptions { getAppConfig: (options?: { role?: string; + userId?: string; tenantId?: string; refresh?: boolean; }) => Promise; @@ -119,6 +120,7 @@ export function createSetBalanceConfig({ const user = req.user as IUser & { _id: string | ObjectId }; const appConfig = await getAppConfig({ role: user?.role, + userId: user?.id, tenantId: user?.tenantId, }); const balanceConfig = getBalanceConfig(appConfig); diff --git a/packages/api/src/middleware/remoteAgentAuth.spec.ts b/packages/api/src/middleware/remoteAgentAuth.spec.ts index 983f7bf706..96b9f30e94 100644 --- a/packages/api/src/middleware/remoteAgentAuth.spec.ts +++ b/packages/api/src/middleware/remoteAgentAuth.spec.ts @@ -429,7 +429,11 @@ describe('createRemoteAgentAuth', () => { await createRemoteAgentAuth(deps)(req as Request, makeRes().res, mockNext); expect(deps.getAppConfig).toHaveBeenNthCalledWith(1, { baseOnly: true }); - expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { tenantId: 'tenant-strict' }); + expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { + role: 'user', + userId: 'uid123', + tenantId: 'tenant-strict', + }); expect(jwt.verify).toHaveBeenCalledTimes(2); expect((jwt.verify as jest.Mock).mock.calls[1][2]).toEqual( expect.objectContaining({ audience: 'tenant-audience' }), @@ -461,7 +465,11 @@ describe('createRemoteAgentAuth', () => { await createRemoteAgentAuth(deps)(req as Request, res, mockNext); expect(deps.getAppConfig).toHaveBeenNthCalledWith(1, { baseOnly: true }); - expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { tenantId: 'tenant-strict' }); + expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { + role: 'user', + userId: 'uid123', + tenantId: 'tenant-strict', + }); expect(jwt.verify).toHaveBeenCalledTimes(2); expect(status).toHaveBeenCalledWith(401); expect(json).toHaveBeenCalledWith({ error: 'Unauthorized' }); @@ -493,7 +501,11 @@ describe('createRemoteAgentAuth', () => { await createRemoteAgentAuth(deps)(req as Request, res, mockNext); expect(deps.getAppConfig).toHaveBeenNthCalledWith(1, { baseOnly: true }); - expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { tenantId: 'tenant-api-key-only' }); + expect(deps.getAppConfig).toHaveBeenNthCalledWith(2, { + role: 'user', + userId: 'uid123', + tenantId: 'tenant-api-key-only', + }); expect(jwt.verify).toHaveBeenCalledTimes(1); expect(status).toHaveBeenCalledWith(401); expect(json).toHaveBeenCalledWith({ error: 'Unauthorized' }); diff --git a/packages/api/src/middleware/remoteAgentAuth.ts b/packages/api/src/middleware/remoteAgentAuth.ts index e51c687c18..2ed7031fe8 100644 --- a/packages/api/src/middleware/remoteAgentAuth.ts +++ b/packages/api/src/middleware/remoteAgentAuth.ts @@ -247,7 +247,7 @@ function getConfigOptions(req: Request): GetAppConfigOptions { } function getUserConfigOptions(user: IUser): GetAppConfigOptions { - if (user.tenantId) return { tenantId: user.tenantId }; + if (user.tenantId) return { role: user.role, userId: user.id, tenantId: user.tenantId }; return { baseOnly: true }; } @@ -255,6 +255,8 @@ function isResolvedUserConfigScope(initialOptions: GetAppConfigOptions, user: IU const userOptions = getUserConfigOptions(user); return ( initialOptions.tenantId === userOptions.tenantId && + initialOptions.userId === userOptions.userId && + initialOptions.role === userOptions.role && initialOptions.baseOnly === userOptions.baseOnly ); }