mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-10 08:31:37 +00:00
Add a short-lived Mongo-backed refresh-flight record so concurrent OBO refreshes for the same OpenID session do not redeem the same rotating refresh token on different workers. The winning worker performs the IdP refresh and stores an encrypted result; joiners wait for that result, hydrate their request session, and return without calling the IdP.
159 lines
4.8 KiB
JavaScript
159 lines
4.8 KiB
JavaScript
jest.mock('@librechat/data-schemas', () => ({
|
|
logger: {
|
|
warn: jest.fn(),
|
|
},
|
|
encryptV2: jest.fn(async (value) => `encrypted:${value}`),
|
|
decryptV2: jest.fn(async (value) => value.replace(/^encrypted:/, '')),
|
|
}));
|
|
|
|
jest.mock('~/models', () => ({
|
|
acquireOpenIDRefreshFlight: jest.fn(),
|
|
completeOpenIDRefreshFlight: jest.fn(),
|
|
failOpenIDRefreshFlight: jest.fn(),
|
|
findOpenIDRefreshFlight: jest.fn(),
|
|
}));
|
|
|
|
const { encryptV2, decryptV2 } = require('@librechat/data-schemas');
|
|
const db = require('~/models');
|
|
const {
|
|
acquireOpenIDRefreshFlight,
|
|
completeOpenIDRefreshFlight,
|
|
createOpenIDRefreshFlightKey,
|
|
failOpenIDRefreshFlight,
|
|
waitForOpenIDRefreshFlight,
|
|
__internals,
|
|
} = require('./OpenIDRefreshFlight');
|
|
|
|
describe('OpenIDRefreshFlight', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
db.acquireOpenIDRefreshFlight.mockResolvedValue({ acquired: true, flight: null });
|
|
db.completeOpenIDRefreshFlight.mockResolvedValue({});
|
|
db.failOpenIDRefreshFlight.mockResolvedValue({});
|
|
db.findOpenIDRefreshFlight.mockResolvedValue(null);
|
|
});
|
|
|
|
it('creates a stable hash key from session, user, issuer, tenant, and refresh token', () => {
|
|
const req = {
|
|
sessionID: 'session-1',
|
|
user: { tenantId: 'tenant-1', openidIssuer: 'issuer-1' },
|
|
};
|
|
const user = { openidId: 'oidc-sub-1' };
|
|
|
|
const keyA = createOpenIDRefreshFlightKey({ req, user, refreshToken: 'rt-old' });
|
|
const keyB = createOpenIDRefreshFlightKey({ req, user, refreshToken: 'rt-old' });
|
|
const keyC = createOpenIDRefreshFlightKey({ req, user, refreshToken: 'rt-other' });
|
|
|
|
expect(keyA).toBe(keyB);
|
|
expect(keyA).not.toBe(keyC);
|
|
expect(keyA).not.toContain('rt-old');
|
|
});
|
|
|
|
it('returns null key when identity or refresh token is unavailable', () => {
|
|
expect(createOpenIDRefreshFlightKey({ req: {}, user: {}, refreshToken: 'rt' })).toBeNull();
|
|
expect(
|
|
createOpenIDRefreshFlightKey({
|
|
req: { user: { id: 'user-1' } },
|
|
user: undefined,
|
|
refreshToken: undefined,
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('acquires a Mongo flight with owner and expiry metadata', async () => {
|
|
const result = await acquireOpenIDRefreshFlight({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
ttl: 60000,
|
|
lockTtl: 30000,
|
|
});
|
|
|
|
expect(result.acquired).toBe(true);
|
|
expect(result.key).toBe('flight-key');
|
|
expect(result.ownerId).toBe('owner-1');
|
|
expect(db.acquireOpenIDRefreshFlight).toHaveBeenCalledWith({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
lockExpiresAt: expect.any(Date),
|
|
expiresAt: expect.any(Date),
|
|
});
|
|
});
|
|
|
|
it('encrypts completed token results before storing them', async () => {
|
|
const tokens = {
|
|
access_token: 'access',
|
|
id_token: 'id',
|
|
refresh_token: 'refresh',
|
|
expires_at: 123,
|
|
};
|
|
|
|
await completeOpenIDRefreshFlight({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
tokens,
|
|
ttl: 60000,
|
|
});
|
|
|
|
expect(encryptV2).toHaveBeenCalledWith(JSON.stringify(tokens));
|
|
expect(db.completeOpenIDRefreshFlight).toHaveBeenCalledWith({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
encryptedResult: `encrypted:${JSON.stringify(tokens)}`,
|
|
expiresAt: expect.any(Date),
|
|
});
|
|
});
|
|
|
|
it('marks a flight failed with a non-sensitive message', async () => {
|
|
await failOpenIDRefreshFlight({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
error: new Error('invalid_grant'),
|
|
ttl: 60000,
|
|
});
|
|
|
|
expect(db.failOpenIDRefreshFlight).toHaveBeenCalledWith({
|
|
key: 'flight-key',
|
|
ownerId: 'owner-1',
|
|
errorMessage: 'invalid_grant',
|
|
expiresAt: expect.any(Date),
|
|
});
|
|
});
|
|
|
|
it('waits for and decrypts a completed flight result', async () => {
|
|
const tokens = { access_token: 'access', refresh_token: 'refresh', expires_at: 123 };
|
|
db.findOpenIDRefreshFlight.mockResolvedValueOnce({
|
|
status: 'completed',
|
|
encryptedResult: `encrypted:${JSON.stringify(tokens)}`,
|
|
});
|
|
|
|
const result = await waitForOpenIDRefreshFlight({
|
|
key: 'flight-key',
|
|
timeoutMs: 1,
|
|
intervalMs: 1,
|
|
});
|
|
|
|
expect(decryptV2).toHaveBeenCalledWith(`encrypted:${JSON.stringify(tokens)}`);
|
|
expect(result).toEqual(tokens);
|
|
});
|
|
|
|
it('throws when another worker records a failed flight', async () => {
|
|
db.findOpenIDRefreshFlight.mockResolvedValueOnce({
|
|
status: 'failed',
|
|
errorMessage: 'invalid_grant',
|
|
});
|
|
|
|
await expect(
|
|
waitForOpenIDRefreshFlight({ key: 'flight-key', timeoutMs: 1, intervalMs: 1 }),
|
|
).rejects.toThrow('invalid_grant');
|
|
});
|
|
|
|
it('exposes completed-flight parsing for focused tests', async () => {
|
|
const tokens = { access_token: 'access' };
|
|
await expect(
|
|
__internals.readCompletedFlight({
|
|
status: 'completed',
|
|
encryptedResult: `encrypted:${JSON.stringify(tokens)}`,
|
|
}),
|
|
).resolves.toEqual(tokens);
|
|
});
|
|
});
|