mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-16 07:25:31 +01:00
473 lines
16 KiB
TypeScript
473 lines
16 KiB
TypeScript
|
|
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<IUser> = {
|
||
|
|
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<IUser> = {
|
||
|
|
...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<IUser> = {
|
||
|
|
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<IUser> = {
|
||
|
|
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<IUser> = {
|
||
|
|
...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<IUser> = {
|
||
|
|
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<IUser> = {
|
||
|
|
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<IUser> = {
|
||
|
|
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');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|