🍪 fix: Validate Shared-File Cookie Auth Against the Live Refresh Session (#13908)

* fix: validate shared file cookie sessions

* fix: run shared file session lookup as system
This commit is contained in:
Danny Avila 2026-06-23 08:32:28 -04:00 committed by GitHub
parent 725a14e409
commit ddc763595a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 25 deletions

View file

@ -1,14 +1,27 @@
const mockVerify = jest.fn();
const mockGetUserById = jest.fn();
const mockFindSession = jest.fn();
const mockRunAsSystem = jest.fn((fn) => fn());
jest.mock('jsonwebtoken', () => ({ verify: (...args) => mockVerify(...args) }));
jest.mock('@librechat/api', () => ({ isEnabled: (v) => v === 'true' || v === true }));
jest.mock('@librechat/data-schemas', () => ({
logger: { warn: jest.fn(), error: jest.fn() },
runAsSystem: (fn) => fn(),
jest.mock('@librechat/api', () => ({ isEnabled: (v) => v === 'true' || v === true }), {
virtual: true,
});
jest.mock(
'@librechat/data-schemas',
() => ({
logger: { warn: jest.fn(), error: jest.fn() },
runAsSystem: (...args) => mockRunAsSystem(...args),
}),
{ virtual: true },
);
jest.mock('librechat-data-provider', () => ({ SystemRoles: { USER: 'USER' } }), {
virtual: true,
});
jest.mock('~/models', () => ({
getUserById: (...args) => mockGetUserById(...args),
findSession: (...args) => mockFindSession(...args),
}));
jest.mock('librechat-data-provider', () => ({ SystemRoles: { USER: 'USER' } }));
jest.mock('~/models', () => ({ getUserById: (...args) => mockGetUserById(...args) }));
const optionalShareFileAuth = require('./optionalShareFileAuth');
@ -30,20 +43,25 @@ describe('optionalShareFileAuth', () => {
expect(next).toHaveBeenCalledTimes(1);
expect(mockVerify).not.toHaveBeenCalled();
expect(mockGetUserById).not.toHaveBeenCalled();
expect(mockFindSession).not.toHaveBeenCalled();
});
it('resolves the viewer from a valid refreshToken cookie', async () => {
it('resolves the viewer from a valid refreshToken cookie with a live session', async () => {
mockVerify.mockReturnValue({ id: 'viewer-1' });
mockFindSession.mockResolvedValue({ _id: 'session-1' });
mockGetUserById.mockResolvedValue({ _id: 'viewer-1', role: 'USER' });
const req = { headers: { cookie: 'refreshToken=good.jwt' } };
const next = await run(req);
expect(next).toHaveBeenCalledTimes(1);
expect(mockVerify).toHaveBeenCalledWith('good.jwt', 'test-secret');
expect(mockFindSession).toHaveBeenCalledWith({ userId: 'viewer-1', refreshToken: 'good.jwt' });
expect(mockRunAsSystem).toHaveBeenCalledTimes(2);
expect(req.user).toMatchObject({ id: 'viewer-1', role: 'USER' });
});
it('defaults the role to USER when the record has none', async () => {
mockVerify.mockReturnValue({ id: 'viewer-2' });
mockFindSession.mockResolvedValue({ _id: 'session-2' });
mockGetUserById.mockResolvedValue({ _id: 'viewer-2' });
const req = { headers: { cookie: 'refreshToken=good.jwt' } };
await run(req);
@ -58,6 +76,21 @@ describe('optionalShareFileAuth', () => {
expect(mockGetUserById).not.toHaveBeenCalled();
});
it('leaves req.user unset when the refresh token has no live session', async () => {
mockVerify.mockReturnValue({ id: 'viewer-3' });
mockFindSession.mockResolvedValue(null);
const req = { headers: { cookie: 'refreshToken=revoked.jwt' } };
const next = await run(req);
expect(next).toHaveBeenCalledTimes(1);
expect(req.user).toBeUndefined();
expect(mockFindSession).toHaveBeenCalledWith({
userId: 'viewer-3',
refreshToken: 'revoked.jwt',
});
expect(mockRunAsSystem).toHaveBeenCalledTimes(1);
expect(mockGetUserById).not.toHaveBeenCalled();
});
it('leaves req.user unset when the token is invalid', async () => {
mockVerify.mockImplementation(() => {
throw new Error('bad token');
@ -69,16 +102,35 @@ describe('optionalShareFileAuth', () => {
expect(mockGetUserById).not.toHaveBeenCalled();
});
it('uses the signed openid_user_id cookie for OpenID-reuse sessions', async () => {
it('uses the signed openid_user_id cookie only for active OpenID-reuse sessions', async () => {
process.env.OPENID_REUSE_TOKENS = 'true';
mockVerify.mockReturnValue({ id: 'oidc-1' });
mockGetUserById.mockResolvedValue({ _id: 'oidc-1', role: 'USER' });
const req = {
headers: { cookie: 'token_provider=openid; openid_user_id=signed.jwt' },
headers: {
cookie: 'token_provider=openid; refreshToken=stored-refresh; openid_user_id=signed.jwt',
},
session: { openidTokens: { refreshToken: 'stored-refresh' } },
};
await run(req);
expect(mockVerify).toHaveBeenCalledWith('signed.jwt', 'test-secret');
expect(mockFindSession).not.toHaveBeenCalled();
expect(req.user).toMatchObject({ id: 'oidc-1' });
delete process.env.OPENID_REUSE_TOKENS;
});
it('leaves req.user unset for OpenID-reuse cookies without an active matching session', async () => {
process.env.OPENID_REUSE_TOKENS = 'true';
mockVerify.mockReturnValue({ id: 'oidc-2' });
const req = {
headers: {
cookie: 'token_provider=openid; refreshToken=stale-refresh; openid_user_id=signed.jwt',
},
session: { openidTokens: { refreshToken: 'current-refresh' } },
};
await run(req);
expect(req.user).toBeUndefined();
expect(mockGetUserById).not.toHaveBeenCalled();
delete process.env.OPENID_REUSE_TOKENS;
});
});