import { describe, it, expect, jest, beforeEach } from '@jest/globals'; import { extractOpenIDTokenInfo, processOpenIDPlaceholders, isOpenIDTokenValid, createBearerAuthHeader, isOpenIDAvailable, type OpenIDTokenInfo, } from '../packages/api/src/utils/oidc'; import { processMCPEnv, resolveHeaders } from '../packages/api/src/utils/env'; import type { TUser } from 'librechat-data-provider'; import type { IUser } from '@librechat/data-schemas'; // Mock logger to avoid console output during tests jest.mock('@librechat/data-schemas', () => ({ logger: { error: jest.fn(), warn: jest.fn(), info: jest.fn(), }, })); describe('OpenID Connect Federated Provider Token Integration', () => { // Mock user with Cognito tokens const mockCognitoUser: Partial = { id: 'user-123', email: 'test@example.com', name: 'Test User', provider: 'openid', openidId: 'cognito-user-123', federatedTokens: { access_token: 'cognito-access-token-123', id_token: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjb2duaXRvLXVzZXItMTIzIiwiZW1haWwiOiJ0ZXN0QGV4YW1wbGUuY29tIiwibmFtZSI6IlRlc3QgVXNlciIsImV4cCI6MTcwMDAwMDAwMH0.fake-signature', expires_at: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour }, }; const mockExpiredCognitoUser: Partial = { ...mockCognitoUser, federatedTokens: { access_token: 'expired-cognito-token', id_token: 'expired-cognito-id-token', expires_at: Math.floor(Date.now() / 1000) - 3600, // Expired 1 hour ago }, }; // Mock user with tokens in alternative location const mockOpenIDTokensUser: Partial = { id: 'user-456', email: 'alt@example.com', name: 'Alt User', provider: 'openid', openidId: 'alt-user-456', openidTokens: { access_token: 'alt-access-token-456', id_token: 'alt-id-token-789', expires_at: Math.floor(Date.now() / 1000) + 3600, }, }; beforeEach(() => { jest.clearAllMocks(); }); describe('extractOpenIDTokenInfo', () => { it('should extract federated provider token info from Cognito user', () => { const tokenInfo = extractOpenIDTokenInfo(mockCognitoUser as IUser); expect(tokenInfo).toEqual({ accessToken: 'cognito-access-token-123', idToken: expect.stringContaining('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9'), expiresAt: expect.any(Number), userId: 'cognito-user-123', userEmail: 'test@example.com', userName: 'Test User', claims: expect.objectContaining({ sub: 'cognito-user-123', email: 'test@example.com', name: 'Test User', }), }); }); it('should extract tokens from alternative storage location', () => { const tokenInfo = extractOpenIDTokenInfo(mockOpenIDTokensUser as IUser); expect(tokenInfo).toEqual({ accessToken: 'alt-access-token-456', idToken: 'alt-id-token-789', expiresAt: expect.any(Number), userId: 'alt-user-456', userEmail: 'alt@example.com', userName: 'Alt User', }); }); it('should return null for non-OpenID user', () => { const nonOpenIDUser: Partial = { id: 'user-123', provider: 'google', email: 'test@example.com', }; const tokenInfo = extractOpenIDTokenInfo(nonOpenIDUser as IUser); expect(tokenInfo).toBeNull(); }); it('should return null for null/undefined user', () => { expect(extractOpenIDTokenInfo(null)).toBeNull(); expect(extractOpenIDTokenInfo(undefined)).toBeNull(); }); it('should handle JWT parsing errors gracefully', () => { const userWithMalformedJWT: Partial = { ...mockCognitoUser, federatedTokens: { access_token: 'valid-access-token', id_token: 'malformed.jwt.token', expires_at: Math.floor(Date.now() / 1000) + 3600, }, }; const tokenInfo = extractOpenIDTokenInfo(userWithMalformedJWT as IUser); expect(tokenInfo).toBeDefined(); expect(tokenInfo?.accessToken).toBe('valid-access-token'); expect(tokenInfo?.claims).toBeUndefined(); }); }); describe('isOpenIDTokenValid', () => { it('should return true for valid Cognito token', () => { const tokenInfo = extractOpenIDTokenInfo(mockCognitoUser as IUser); expect(isOpenIDTokenValid(tokenInfo)).toBe(true); }); it('should return false for expired Cognito token', () => { const tokenInfo = extractOpenIDTokenInfo(mockExpiredCognitoUser as IUser); expect(isOpenIDTokenValid(tokenInfo)).toBe(false); }); it('should return false for null token info', () => { expect(isOpenIDTokenValid(null)).toBe(false); }); it('should return false for token info without access token', () => { const tokenInfo: OpenIDTokenInfo = { userId: 'user-123', userEmail: 'test@example.com', }; expect(isOpenIDTokenValid(tokenInfo)).toBe(false); }); }); describe('processOpenIDPlaceholders', () => { const tokenInfo: OpenIDTokenInfo = { accessToken: 'cognito-access-token-123', idToken: 'cognito-id-token-456', userId: 'cognito-user-789', userEmail: 'cognito@example.com', userName: 'Cognito User', expiresAt: 1700000000, }; it('should replace OpenID Connect token placeholders', () => { const template = 'Bearer {{LIBRECHAT_OPENID_TOKEN}}'; const result = processOpenIDPlaceholders(template, tokenInfo); expect(result).toBe('Bearer cognito-access-token-123'); }); it('should replace specific OpenID Connect placeholders', () => { const template = ` Access: {{LIBRECHAT_OPENID_ACCESS_TOKEN}} ID: {{LIBRECHAT_OPENID_ID_TOKEN}} User: {{LIBRECHAT_OPENID_USER_ID}} Email: {{LIBRECHAT_OPENID_USER_EMAIL}} Name: {{LIBRECHAT_OPENID_USER_NAME}} Expires: {{LIBRECHAT_OPENID_EXPIRES_AT}} `; const result = processOpenIDPlaceholders(template, tokenInfo); expect(result).toContain('Access: cognito-access-token-123'); expect(result).toContain('ID: cognito-id-token-456'); expect(result).toContain('User: cognito-user-789'); expect(result).toContain('Email: cognito@example.com'); expect(result).toContain('Name: Cognito User'); expect(result).toContain('Expires: 1700000000'); }); it('should handle missing token fields gracefully', () => { const partialTokenInfo: OpenIDTokenInfo = { accessToken: 'partial-cognito-token', userId: 'user-123', }; const template = 'Token: {{LIBRECHAT_OPENID_TOKEN}}, Email: {{LIBRECHAT_OPENID_USER_EMAIL}}'; const result = processOpenIDPlaceholders(template, partialTokenInfo); expect(result).toBe('Token: partial-cognito-token, Email: '); }); it('should return original value for null token info', () => { const template = 'Bearer {{LIBRECHAT_OPENID_TOKEN}}'; const result = processOpenIDPlaceholders(template, null); expect(result).toBe(template); }); }); describe('createBearerAuthHeader', () => { it('should create proper Bearer header with Cognito token', () => { const tokenInfo: OpenIDTokenInfo = { accessToken: 'cognito-test-token-123', }; const header = createBearerAuthHeader(tokenInfo); expect(header).toBe('Bearer cognito-test-token-123'); }); it('should return empty string for null token info', () => { const header = createBearerAuthHeader(null); expect(header).toBe(''); }); it('should return empty string for token info without access token', () => { const tokenInfo: OpenIDTokenInfo = { userId: 'user-123', }; const header = createBearerAuthHeader(tokenInfo); expect(header).toBe(''); }); }); describe('isOpenIDAvailable', () => { const originalEnv = process.env; beforeEach(() => { jest.resetModules(); process.env = { ...originalEnv }; }); afterAll(() => { process.env = originalEnv; }); it('should return true when OpenID Connect is properly configured for Cognito', () => { process.env.OPENID_CLIENT_ID = 'cognito-client-id'; process.env.OPENID_CLIENT_SECRET = 'cognito-client-secret'; process.env.OPENID_ISSUER = 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ABC123'; expect(isOpenIDAvailable()).toBe(true); }); it('should return false when OpenID Connect is not configured', () => { delete process.env.OPENID_CLIENT_ID; delete process.env.OPENID_CLIENT_SECRET; delete process.env.OPENID_ISSUER; expect(isOpenIDAvailable()).toBe(false); }); it('should return false when OpenID Connect is partially configured', () => { process.env.OPENID_CLIENT_ID = 'cognito-client-id'; delete process.env.OPENID_CLIENT_SECRET; delete process.env.OPENID_ISSUER; expect(isOpenIDAvailable()).toBe(false); }); }); describe('Integration with resolveHeaders', () => { it('should resolve OpenID Connect placeholders in headers for Cognito', () => { const headers = { 'Authorization': '{{LIBRECHAT_OPENID_TOKEN}}', 'X-User-ID': '{{LIBRECHAT_OPENID_USER_ID}}', 'X-User-Email': '{{LIBRECHAT_OPENID_USER_EMAIL}}', }; const resolvedHeaders = resolveHeaders({ headers, user: mockCognitoUser as TUser, }); expect(resolvedHeaders['Authorization']).toBe('cognito-access-token-123'); expect(resolvedHeaders['X-User-ID']).toBe('cognito-user-123'); expect(resolvedHeaders['X-User-Email']).toBe('test@example.com'); }); it('should work with Bearer token format for Cognito', () => { const headers = { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_TOKEN}}', }; const resolvedHeaders = resolveHeaders({ headers, user: mockCognitoUser as TUser, }); expect(resolvedHeaders['Authorization']).toBe('Bearer cognito-access-token-123'); }); it('should work with specific access token placeholder', () => { const headers = { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_ACCESS_TOKEN}}', 'X-Cognito-ID-Token': '{{LIBRECHAT_OPENID_ID_TOKEN}}', }; const resolvedHeaders = resolveHeaders({ headers, user: mockCognitoUser as TUser, }); expect(resolvedHeaders['Authorization']).toBe('Bearer cognito-access-token-123'); expect(resolvedHeaders['X-Cognito-ID-Token']).toContain('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9'); }); }); describe('Integration with processMCPEnv', () => { it('should process OpenID Connect placeholders in MCP environment variables for Cognito', () => { const mcpOptions = { command: 'node', args: ['server.js'], env: { 'COGNITO_ACCESS_TOKEN': '{{LIBRECHAT_OPENID_TOKEN}}', 'USER_ID': '{{LIBRECHAT_OPENID_USER_ID}}', 'USER_EMAIL': '{{LIBRECHAT_OPENID_USER_EMAIL}}', }, }; const processedOptions = processMCPEnv({ options: mcpOptions, user: mockCognitoUser as TUser, }); expect(processedOptions.env?.['COGNITO_ACCESS_TOKEN']).toBe('cognito-access-token-123'); expect(processedOptions.env?.['USER_ID']).toBe('cognito-user-123'); expect(processedOptions.env?.['USER_EMAIL']).toBe('test@example.com'); }); it('should process OpenID Connect placeholders in MCP headers for HTTP transport', () => { const mcpOptions = { type: 'sse' as const, url: 'https://api.example.com/mcp', headers: { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_ACCESS_TOKEN}}', 'X-Cognito-User-Info': '{{LIBRECHAT_OPENID_USER_EMAIL}}', 'X-Cognito-ID-Token': '{{LIBRECHAT_OPENID_ID_TOKEN}}', }, }; const processedOptions = processMCPEnv({ options: mcpOptions, user: mockCognitoUser as TUser, }); expect(processedOptions.headers?.['Authorization']).toBe('Bearer cognito-access-token-123'); expect(processedOptions.headers?.['X-Cognito-User-Info']).toBe('test@example.com'); expect(processedOptions.headers?.['X-Cognito-ID-Token']).toContain('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9'); }); it('should handle AWS-specific MCP server configuration', () => { const awsMcpOptions = { command: 'node', args: ['aws-mcp-server.js'], env: { 'AWS_COGNITO_TOKEN': '{{LIBRECHAT_OPENID_ACCESS_TOKEN}}', 'AWS_COGNITO_ID_TOKEN': '{{LIBRECHAT_OPENID_ID_TOKEN}}', 'COGNITO_USER_SUB': '{{LIBRECHAT_OPENID_USER_ID}}', }, }; const processedOptions = processMCPEnv({ options: awsMcpOptions, user: mockCognitoUser as TUser, }); expect(processedOptions.env?.['AWS_COGNITO_TOKEN']).toBe('cognito-access-token-123'); expect(processedOptions.env?.['AWS_COGNITO_ID_TOKEN']).toContain('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9'); expect(processedOptions.env?.['COGNITO_USER_SUB']).toBe('cognito-user-123'); }); }); describe('Security and Edge Cases', () => { it('should not process OpenID Connect placeholders for expired tokens', () => { const headers = { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_TOKEN}}', }; const resolvedHeaders = resolveHeaders({ headers, user: mockExpiredCognitoUser as TUser, }); // Should not replace placeholder if token is expired expect(resolvedHeaders['Authorization']).toBe('Bearer {{LIBRECHAT_OPENID_TOKEN}}'); }); it('should handle malformed federated token data gracefully', () => { const malformedUser: Partial = { id: 'user-123', provider: 'openid', openidId: 'cognito-user', federatedTokens: null, // Malformed tokens }; const headers = { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_TOKEN}}', }; const resolvedHeaders = resolveHeaders({ headers, user: malformedUser as TUser, }); // Should not replace placeholder if token extraction fails expect(resolvedHeaders['Authorization']).toBe('Bearer {{LIBRECHAT_OPENID_TOKEN}}'); }); it('should handle multiple placeholder instances in same string', () => { const template = '{{LIBRECHAT_OPENID_TOKEN}}-{{LIBRECHAT_OPENID_TOKEN}}-{{LIBRECHAT_OPENID_USER_ID}}'; const tokenInfo: OpenIDTokenInfo = { accessToken: 'cognito-token123', userId: 'cognito-user456', }; const result = processOpenIDPlaceholders(template, tokenInfo); expect(result).toBe('cognito-token123-cognito-token123-cognito-user456'); }); it('should handle users without federated tokens storage', () => { const userWithoutTokens: Partial = { id: 'user-789', provider: 'openid', openidId: 'user-without-tokens', email: 'no-tokens@example.com', // No federatedTokens or openidTokens }; const headers = { 'Authorization': 'Bearer {{LIBRECHAT_OPENID_TOKEN}}', }; const resolvedHeaders = resolveHeaders({ headers, user: userWithoutTokens as TUser, }); // Should not replace placeholder if no tokens available expect(resolvedHeaders['Authorization']).toBe('Bearer {{LIBRECHAT_OPENID_TOKEN}}'); }); it('should prioritize federatedTokens over openidTokens', () => { const userWithBothTokens: Partial = { id: 'user-priority', provider: 'openid', openidId: 'priority-user', federatedTokens: { access_token: 'federated-priority-token', expires_at: Math.floor(Date.now() / 1000) + 3600, }, openidTokens: { access_token: 'openid-fallback-token', expires_at: Math.floor(Date.now() / 1000) + 3600, }, }; const tokenInfo = extractOpenIDTokenInfo(userWithBothTokens as IUser); expect(tokenInfo?.accessToken).toBe('federated-priority-token'); }); }); });