mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-03 22:32:42 +00:00
🤝 fix: Honor OPENID_REUSE_TOKENS in Admin OAuth Exchange (#13154)
This commit is contained in:
parent
89d10a0b83
commit
ac163c2212
2 changed files with 154 additions and 1 deletions
|
|
@ -45,7 +45,9 @@ function createOAuthHandler(redirectUri = domains.client) {
|
|||
|
||||
/** Get refresh token from tokenset for OpenID users */
|
||||
const refreshToken =
|
||||
req.user.tokenset?.refresh_token || req.user.federatedTokens?.refresh_token;
|
||||
req.user.provider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS) === true
|
||||
? req.user.tokenset?.refresh_token || req.user.federatedTokens?.refresh_token
|
||||
: undefined;
|
||||
const expiresAt = Date.now() + sessionExpiry;
|
||||
|
||||
const callbackUrl = new URL(redirectUri);
|
||||
|
|
|
|||
151
api/server/controllers/auth/oauth.spec.js
Normal file
151
api/server/controllers/auth/oauth.spec.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
const mockIsEnabled = jest.fn();
|
||||
const mockGetAdminPanelUrl = jest.fn();
|
||||
const mockIsAdminPanelRedirect = jest.fn();
|
||||
const mockGenerateAdminExchangeCode = jest.fn();
|
||||
const mockSyncUserEntraGroupMemberships = jest.fn();
|
||||
const mockSetAuthTokens = jest.fn();
|
||||
const mockSetOpenIDAuthTokens = jest.fn();
|
||||
const mockGetLogStores = jest.fn();
|
||||
const mockCheckBan = jest.fn();
|
||||
const mockGenerateToken = jest.fn();
|
||||
const mockLogger = { info: jest.fn(), error: jest.fn() };
|
||||
|
||||
jest.mock('librechat-data-provider', () => ({
|
||||
CacheKeys: { ADMIN_OAUTH_EXCHANGE: 'admin-oauth-exchange' },
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/data-schemas', () => ({
|
||||
logger: mockLogger,
|
||||
DEFAULT_SESSION_EXPIRY: 60000,
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/api', () => ({
|
||||
isEnabled: (...args) => mockIsEnabled(...args),
|
||||
getAdminPanelUrl: (...args) => mockGetAdminPanelUrl(...args),
|
||||
isAdminPanelRedirect: (...args) => mockIsAdminPanelRedirect(...args),
|
||||
generateAdminExchangeCode: (...args) => mockGenerateAdminExchangeCode(...args),
|
||||
}));
|
||||
|
||||
jest.mock('~/server/services/PermissionService', () => ({
|
||||
syncUserEntraGroupMemberships: (...args) => mockSyncUserEntraGroupMemberships(...args),
|
||||
}));
|
||||
|
||||
jest.mock('~/server/services/AuthService', () => ({
|
||||
setAuthTokens: (...args) => mockSetAuthTokens(...args),
|
||||
setOpenIDAuthTokens: (...args) => mockSetOpenIDAuthTokens(...args),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'~/cache/getLogStores',
|
||||
() =>
|
||||
(...args) =>
|
||||
mockGetLogStores(...args),
|
||||
);
|
||||
|
||||
jest.mock('~/server/middleware', () => ({
|
||||
checkBan: (...args) => mockCheckBan(...args),
|
||||
}));
|
||||
|
||||
jest.mock('~/models', () => ({
|
||||
generateToken: (...args) => mockGenerateToken(...args),
|
||||
}));
|
||||
|
||||
const { createOAuthHandler } = require('./oauth');
|
||||
|
||||
const ORIGINAL_ENV = process.env;
|
||||
|
||||
function buildReq(overrides = {}) {
|
||||
return {
|
||||
user: {
|
||||
_id: 'user-123',
|
||||
email: 'admin@example.com',
|
||||
provider: 'openid',
|
||||
tokenset: { refresh_token: 'openid-refresh-token', access_token: 'openid-access-token' },
|
||||
federatedTokens: { refresh_token: 'federated-refresh-token' },
|
||||
},
|
||||
pkceChallenge: 'pkce-challenge',
|
||||
banned: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRes() {
|
||||
return {
|
||||
headersSent: false,
|
||||
redirect: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
describe('createOAuthHandler', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = {
|
||||
...ORIGINAL_ENV,
|
||||
DOMAIN_CLIENT: 'http://localhost:3080',
|
||||
DOMAIN_SERVER: 'http://localhost:3080',
|
||||
OPENID_REUSE_TOKENS: 'false',
|
||||
};
|
||||
mockIsEnabled.mockImplementation((value) => value === 'true' || value === true);
|
||||
mockGetAdminPanelUrl.mockReturnValue('http://admin.example.com');
|
||||
mockIsAdminPanelRedirect.mockReturnValue(true);
|
||||
mockGetLogStores.mockReturnValue({});
|
||||
mockCheckBan.mockResolvedValue(undefined);
|
||||
mockGenerateToken.mockResolvedValue('jwt-token');
|
||||
mockGenerateAdminExchangeCode.mockResolvedValue('exchange-code');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = ORIGINAL_ENV;
|
||||
});
|
||||
|
||||
it('omits refresh token from admin exchange when OPENID_REUSE_TOKENS is disabled', async () => {
|
||||
const handler = createOAuthHandler('http://admin.example.com/auth/openid/callback');
|
||||
const req = buildReq();
|
||||
const res = buildRes();
|
||||
const next = jest.fn();
|
||||
|
||||
await handler(req, res, next);
|
||||
|
||||
expect(mockGenerateAdminExchangeCode).toHaveBeenCalledWith(
|
||||
{},
|
||||
req.user,
|
||||
'jwt-token',
|
||||
undefined,
|
||||
'http://admin.example.com',
|
||||
'pkce-challenge',
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(res.redirect).toHaveBeenCalledWith(
|
||||
'http://admin.example.com/auth/openid/callback?code=exchange-code',
|
||||
);
|
||||
expect(mockSetOpenIDAuthTokens).not.toHaveBeenCalled();
|
||||
expect(mockSetAuthTokens).not.toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('includes refresh token in admin exchange when OPENID_REUSE_TOKENS is enabled', async () => {
|
||||
process.env.OPENID_REUSE_TOKENS = 'true';
|
||||
const handler = createOAuthHandler('http://admin.example.com/auth/openid/callback');
|
||||
const req = buildReq();
|
||||
const res = buildRes();
|
||||
const next = jest.fn();
|
||||
|
||||
await handler(req, res, next);
|
||||
|
||||
expect(mockGenerateAdminExchangeCode).toHaveBeenCalledWith(
|
||||
{},
|
||||
req.user,
|
||||
'jwt-token',
|
||||
'openid-refresh-token',
|
||||
'http://admin.example.com',
|
||||
'pkce-challenge',
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(res.redirect).toHaveBeenCalledWith(
|
||||
'http://admin.example.com/auth/openid/callback?code=exchange-code',
|
||||
);
|
||||
expect(mockSetOpenIDAuthTokens).not.toHaveBeenCalled();
|
||||
expect(mockSetAuthTokens).not.toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue