diff --git a/src/lib/routes/logout.test.ts b/src/lib/routes/logout.test.ts index b5b0587a41..8756676117 100644 --- a/src/lib/routes/logout.test.ts +++ b/src/lib/routes/logout.test.ts @@ -115,6 +115,42 @@ test('should call destroy on session', async () => { expect(fakeSession.destroy.mock.calls.length).toBe(1); }); +test('should handle req.logout with callback function', async () => { + // passport >=0.6.0 + const baseUriPath = ''; + const logoutFunction = jest.fn((cb: (err?: any) => void) => cb()); + const app = express(); + const config = createTestConfig({ server: { baseUriPath } }); + app.use((req: IAuthRequest, res, next) => { + req.logout = logoutFunction; + next(); + }); + app.use('/logout', new LogoutController(config).router); + const request = supertest(app); + await request.get(`${baseUriPath}/logout`); + + expect(logoutFunction).toHaveBeenCalledTimes(1); + expect(logoutFunction).toHaveBeenCalledWith(expect.anything()); +}); + +test('should handle req.logout without callback function', async () => { + // passport <0.6.0 + const baseUriPath = ''; + const logoutFunction = jest.fn(); + const app = express(); + const config = createTestConfig({ server: { baseUriPath } }); + app.use((req: IAuthRequest, res, next) => { + req.logout = logoutFunction; + next(); + }); + app.use('/logout', new LogoutController(config).router); + const request = supertest(app); + await request.get(`${baseUriPath}/logout`); + + expect(logoutFunction).toHaveBeenCalledTimes(1); + expect(logoutFunction).toHaveBeenCalledWith(); +}); + test('should redirect to alternative logoutUrl', async () => { const fakeSession = { destroy: jest.fn(), diff --git a/src/lib/routes/logout.ts b/src/lib/routes/logout.ts index ba5b45bf89..cdf5388ca0 100644 --- a/src/lib/routes/logout.ts +++ b/src/lib/routes/logout.ts @@ -1,4 +1,5 @@ import { Response } from 'express'; +import { promisify } from 'util'; import { IUnleashConfig } from '../types/option'; import Controller from './controller'; import { IAuthRequest } from './unleash-types'; @@ -25,14 +26,23 @@ class LogoutController extends Controller { res.redirect(req.session.logoutUrl); return; } - - req.session.destroy(); } if (req.logout) { - req.logout(); + if (this.isReqLogoutWithoutCallback(req.logout)) { + // passport < 0.6.0 + req.logout(); + } else { + // for passport >= 0.6.0, a callback function is expected as first argument. + // to reuse controller error handling, function is turned into a promise + const logoutAsyncFn = promisify(req.logout).bind(req); + await logoutAsyncFn(); + } } + if (req.session) { + req.session.destroy(); + } res.clearCookie(this.cookieName); if (this.clearSiteDataOnLogout) { @@ -41,6 +51,12 @@ class LogoutController extends Controller { res.redirect(`${this.baseUri}/`); } + + private isReqLogoutWithoutCallback( + logout: IAuthRequest['logout'], + ): logout is () => void { + return logout.length === 0; + } } export default LogoutController; diff --git a/src/lib/routes/unleash-types.ts b/src/lib/routes/unleash-types.ts index 7f6af2131f..4c684edf02 100644 --- a/src/lib/routes/unleash-types.ts +++ b/src/lib/routes/unleash-types.ts @@ -8,7 +8,7 @@ export interface IAuthRequest< ReqQuery = any, > extends Request { user: User; - logout: () => void; + logout: (() => void) | ((callback: (err?: any) => void) => void); session: any; }