UNPKG

forest-express

Version:

Official package for all Forest Express Lianas

271 lines (227 loc) 8.03 kB
const { inject } = require('@forestadmin/context'); const sinon = require('sinon'); const jsonwebtoken = require('jsonwebtoken'); const { request } = require('../helpers/request'); const createServer = require('../helpers/create-server'); const envSecret = Array(65).join('0'); const authSecret = Array(65).join('1'); async function setupApp() { const sandbox = sinon.createSandbox(); const forestApp = await createServer(envSecret, authSecret); const injections = inject(); sandbox.stub(injections.forestServerRequester, 'perform'); const oidcConfig = { issuer: 'forest-admin', authorization_endpoint: 'https://app.forestadmin.com/oidc/authorization', registration_endpoint: 'https://app.forestadmin.com/oidc/registration', }; return { forestApp, sandbox, injections, oidcConfig, }; } function mockOpenIdClient(sandbox) { const client = { callback: sinon.stub(), authorizationUrl: sinon.stub(), }; const issuer = { Client: sinon.stub().returns(client), }; issuer.Client.register = sinon.stub().resolves(client); const injections = inject(); injections.oidcClientManagerService.clearCache(); sandbox.stub(injections.openIdClient, 'Issuer').returns(issuer); injections.oidcConfigurationRetrieverService.clearCache(); return { client, issuer }; } describe('routes > authentication', () => { describe('#POST /forest/authentication', () => { /** * @param {import('superagent').SuperAgentRequest} testedRequest * @param {*} body * @param {{ * sandbox: import('sinon').SinonSandbox; * injections: any; * oidcConfig: any; * }} param2 * @returns {Promise<import('superagent').Response>} */ async function setupTest(testedRequest, body, { sandbox, injections, oidcConfig }) { const { client } = mockOpenIdClient(sandbox); client.authorizationUrl.returns('https://authorization'); injections.forestServerRequester.perform .withArgs(sinon.match(/^\/oidc\//)) .resolves(oidcConfig); return new Promise((resolve, reject) => { testedRequest .send(body) .end((error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); } it('should return a valid authentication url', async () => { const { forestApp: app, sandbox, injections, oidcConfig, } = await setupApp(); try { const test = request(app).post('/forest/authentication'); const receivedResponse = await setupTest(test, { renderingId: 42 }, { sandbox, injections, oidcConfig, }); expect(receivedResponse.status).toBe(200); expect(receivedResponse.body.authorizationUrl).toBe('https://authorization'); } finally { sandbox.restore(); } }); it('should return a valid authentication url even with an invalid token in the query', async () => { const { forestApp: app, sandbox, injections, oidcConfig, } = await setupApp(); try { const test = request(app).post('/forest/authentication') .set('Cookie', 'forest_session_token=INVALID_TOKEN'); const receivedResponse = await setupTest(test, { renderingId: 42 }, { sandbox, injections, oidcConfig, }); expect(receivedResponse.status).toBe(200); } finally { sandbox.restore(); } }); }); describe('#GET /forest/authentication/callback', () => { /** * @param {import('superagent').SuperAgentRequest} testedRequest * @param {{ * sandbox: import('sinon').SinonSandbox; * injections: any; * oidcConfig: any; * }} mocks * @returns {{ * receivedResponse: Promise<import('superagent').Response>; * issuer: any; * }} */ async function setupTest(testedRequest, { sandbox, injections, oidcConfig, }) { const { client, issuer } = mockOpenIdClient(sandbox); client.callback.resolves({ access_token: 'THE-ACCESS-TOKEN', }); issuer.Client.register.resolves(client); const performResponse = { data: { id: 666, attributes: { first_name: 'Alice', last_name: 'Doe', email: 'alice@forestadmin.com', teams: [1, 2, 3], }, }, }; injections.forestServerRequester.perform .withArgs(sinon.match(/^\/oidc\//)) .resolves(oidcConfig); injections.forestServerRequester.perform .withArgs(sinon.match(/^\/liana\/v2/), sinon.match.any, sinon.match.any, sinon.match.any) .resolves(performResponse); const receivedResponse = await new Promise((resolve, reject) => { testedRequest .send() .end((error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); return { receivedResponse, issuer }; } it('should return a new authentication token', async () => { const { forestApp: app, sandbox, injections, oidcConfig, } = await setupApp(); try { const test = request(app).get(`/forest/authentication/callback?code=THE-CODE&state=${ encodeURIComponent(JSON.stringify({ renderingId: 42 })) }`); const { receivedResponse, issuer } = await setupTest(test, { sandbox, oidcConfig, injections, app, }); expect(receivedResponse.status).toBe(200); expect(receivedResponse.text).not.toHaveLength(0); const { token } = (await receivedResponse).body; const decoded = jsonwebtoken.verify(token, authSecret); const expectedTokenData = { id: 666, email: 'alice@forestadmin.com', renderingId: 42, firstName: 'Alice', lastName: 'Doe', team: 1, }; expect(decoded).toMatchObject(expectedTokenData); expect(JSON.parse(receivedResponse.text)).toStrictEqual({ token, tokenData: decoded }); expect(injections.forestServerRequester.perform.args[0]).toStrictEqual([ '/oidc/.well-known/openid-configuration', ]); expect(injections.forestServerRequester.perform.args[1]).toStrictEqual([ '/liana/v2/renderings/42/authorization', envSecret, null, { 'forest-token': 'THE-ACCESS-TOKEN' }, ]); expect(issuer.Client.register.firstCall.args).toStrictEqual([{ client_id: undefined, redirect_uris: [ 'http://localhost:3310/forest/authentication/callback', ], token_endpoint_auth_method: 'none', }, { initialAccessToken: envSecret, }]); } finally { sandbox.restore(); } }); it('should return a new authentication token even if the previously received token is invalid', async () => { const { forestApp: app, sandbox, injections, oidcConfig, } = await setupApp(); try { const test = request(app).get(`/forest/authentication/callback?code=THE-CODE&state=${ encodeURIComponent(JSON.stringify({ renderingId: 42 })) }`) .set('Cookie', 'forest_session_token=INVALID'); const { receivedResponse } = await setupTest(test, { sandbox, oidcConfig, injections, app, }); expect(receivedResponse.status).toBe(200); } finally { sandbox.restore(); } }); }); describe('#POST /forest/authentication/logout', () => { it('should return 204', async () => { const { forestApp: app, sandbox, } = await setupApp(); try { const response = await request(app).post('/forest/authentication/logout').send(); expect(response.status).toBe(204); } finally { sandbox.restore(); } }); }); });