From bdd2c692833dc9e974b92088da48d06647bcea66 Mon Sep 17 00:00:00 2001 From: Vilhelm Date: Fri, 12 Jun 2026 13:49:13 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8C=20fix:=20Improve=20robustness=20fo?= =?UTF-8?q?r=20missing=20token=20and=20key=20expiry=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensure `id_token` is populated by falling back to the raw bearer token if explicitly missing from the IdP response. Allow user-provided API keys to be resolved even when the `expiresAt` field is absent. --- api/strategies/openIdJwtStrategy.js | 2 +- api/strategies/openIdJwtStrategy.spec.js | 4 +-- .../src/endpoints/openai/initialize.spec.ts | 27 +++++++++++++++++++ .../api/src/endpoints/openai/initialize.ts | 6 +++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/api/strategies/openIdJwtStrategy.js b/api/strategies/openIdJwtStrategy.js index ab1bcd1c0a1..24facf91882 100644 --- a/api/strategies/openIdJwtStrategy.js +++ b/api/strategies/openIdJwtStrategy.js @@ -151,7 +151,7 @@ const openIdJwtLogin = (openIdConfig) => { user.federatedTokens = { access_token: accessToken || rawToken, - id_token: idToken, + id_token: idToken || rawToken, refresh_token: refreshToken, expires_at: payload.exp, }; diff --git a/api/strategies/openIdJwtStrategy.spec.js b/api/strategies/openIdJwtStrategy.spec.js index 4a1871110c5..286320eecd8 100644 --- a/api/strategies/openIdJwtStrategy.spec.js +++ b/api/strategies/openIdJwtStrategy.spec.js @@ -315,7 +315,7 @@ describe('openIdJwtStrategy – token source handling', () => { expect(user.federatedTokens.refresh_token).toBe('cookie-refresh'); }); - it('should set id_token to undefined when not available in session or cookies', async () => { + it('should fall back to raw Bearer token for id_token when not available in session or cookies', async () => { const req = { headers: { authorization: 'Bearer raw-bearer-token', @@ -326,7 +326,7 @@ describe('openIdJwtStrategy – token source handling', () => { const { user } = await invokeVerify(req, payload); expect(user.federatedTokens.access_token).toBe('cookie-access'); - expect(user.federatedTokens.id_token).toBeUndefined(); + expect(user.federatedTokens.id_token).toBe('raw-bearer-token'); expect(user.federatedTokens.refresh_token).toBe('cookie-refresh'); }); diff --git a/packages/api/src/endpoints/openai/initialize.spec.ts b/packages/api/src/endpoints/openai/initialize.spec.ts index 85c8c1e8965..3081205f708 100644 --- a/packages/api/src/endpoints/openai/initialize.spec.ts +++ b/packages/api/src/endpoints/openai/initialize.spec.ts @@ -115,6 +115,33 @@ describe('initializeOpenAI – SSRF guard wiring', () => { expect(mockValidateEndpointURL).not.toHaveBeenCalled(); }); + it('should resolve a stored user key even when the expiry field is missing', async () => { + const params = createParams({ + OPENAI_API_KEY: AuthType.USER_PROVIDED, + OPENAI_REVERSE_PROXY: 'https://user-proxy.example.com/v1', + }); + params.req.body = {} as BaseInitializeParams['req']['body']; + + try { + await initializeOpenAI(params); + } finally { + (params as unknown as { _restore: () => void })._restore(); + } + + expect(params.db.getUserKeyValues).toHaveBeenCalledWith({ + userId: 'user-1', + name: EModelEndpoint.openAI, + }); + expect(mockValidateEndpointURL).not.toHaveBeenCalled(); + expect(mockGetOpenAIConfig).toHaveBeenCalledWith( + 'sk-user-key', + expect.objectContaining({ + reverseProxyUrl: 'https://user-proxy.example.com/v1', + }), + EModelEndpoint.openAI, + ); + }); + it('should propagate SSRF rejection from validateEndpointURL', async () => { mockValidateEndpointURL.mockRejectedValueOnce( new Error('Base URL for openAI targets a restricted address.'), diff --git a/packages/api/src/endpoints/openai/initialize.ts b/packages/api/src/endpoints/openai/initialize.ts index 1b852deb1fa..addf627ea6a 100644 --- a/packages/api/src/endpoints/openai/initialize.ts +++ b/packages/api/src/endpoints/openai/initialize.ts @@ -44,8 +44,10 @@ export async function initializeOpenAI({ const userProvidesURL = isUserProvided(baseURLOptions[endpoint as keyof typeof baseURLOptions]); let userValues: UserKeyValues | null = null; - if (expiresAt && (userProvidesKey || userProvidesURL)) { - checkUserKeyExpiry(expiresAt, endpoint); + if (userProvidesKey || userProvidesURL) { + if (expiresAt) { + checkUserKeyExpiry(expiresAt, endpoint); + } userValues = await db.getUserKeyValues({ userId: req.user?.id ?? '', name: endpoint }); }