/** * @license * Copyright 2019-2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as chai from 'chai'; import chaiAsPromised = require('chai-as-promised'); import * as ejs from 'ejs'; import * as fs from 'fs'; import * as path from 'path'; import * as request from 'request'; import * as sinon from 'sinon'; import { LoginServer } from '../../build/auth/server'; import * as utils from '../../build/auth/utils'; import tokens from './tokens'; chai.use(chaiAsPromised); const { expect } = chai; async function getPage(name: string): Promise { const pagePath = path.join( __dirname, '..', '..', 'build', 'auth', 'pages', `${name}.ejs`, ); const tpl = fs.readFileSync(pagePath, { encoding: 'utf8' }); const compiledTpl = ejs.compile(tpl); return compiledTpl(); } describe('Login server:', function () { let server: LoginServer; let addr: { host: string; port: number; urlPath: string }; this.beforeEach(async () => { server = new LoginServer(); await server.start(); addr = server.getAddress(); expect(addr.host).to.equal('127.0.0.1'); }); this.afterEach(() => { server.shutdown(); }); async function testLogin(opt: { expectedBody: string; expectedErrorMsg?: string; expectedStatusCode: number; expectedToken: string; urlPath?: string; verb?: string; }) { opt.urlPath = opt.urlPath ?? addr.urlPath; const post = opt.verb ? ((request as any)[opt.verb] as typeof request.post) : request.post; await new Promise((resolve, reject) => { post( `http://${addr.host}:${addr.port}${opt.urlPath}`, { form: { token: opt.expectedToken, }, }, function (error, response, body) { try { expect(error).to.not.exist; expect(response.statusCode).to.equal(opt.expectedStatusCode); expect(body).to.equal(opt.expectedBody); resolve(); } catch (err) { reject(err); } }, ); }); try { const token = await server.awaitForToken(); if (opt.expectedErrorMsg) { throw new Error('Error not thrown when expected'); } else { expect(token).to.exist; expect(token).to.equal(opt.expectedToken); } } catch (err) { if (opt.expectedErrorMsg) { expect(err).to.have.property('message', opt.expectedErrorMsg); } else { throw err; } } } it('should get 404 if posting to an unknown path', async () => { await testLogin({ expectedBody: 'Not found', expectedStatusCode: 404, expectedToken: tokens.johndoe.token, expectedErrorMsg: 'Unknown path or verb', urlPath: '/foobarbaz', }); }); it('should get 404 if not using the correct verb', async () => { await testLogin({ expectedBody: 'Not found', expectedStatusCode: 404, expectedToken: tokens.johndoe.token, expectedErrorMsg: 'Unknown path or verb', verb: 'get', }); }); describe('given the token authenticates with the server', function () { beforeEach(function () { this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); this.loginIfTokenValidStub.returns(Promise.resolve(true)); }); afterEach(function () { this.loginIfTokenValidStub.restore(); }); it('should eventually be the token', async () => { await testLogin({ expectedBody: await getPage('success'), expectedStatusCode: 200, expectedToken: tokens.johndoe.token, }); }); }); describe('given the token does not authenticate with the server', function () { beforeEach(function () { this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); return this.loginIfTokenValidStub.returns(Promise.resolve(false)); }); afterEach(function () { return this.loginIfTokenValidStub.restore(); }); it('should be rejected', async () => { await testLogin({ expectedBody: await getPage('error'), expectedStatusCode: 401, expectedToken: tokens.johndoe.token, expectedErrorMsg: 'Invalid token', }); }); it('should be rejected if no token', async () => { await testLogin({ expectedBody: await getPage('error'), expectedStatusCode: 401, expectedToken: '', expectedErrorMsg: 'No token', }); }); it('should be rejected if token is malformed', async () => { await testLogin({ expectedBody: await getPage('error'), expectedStatusCode: 401, expectedToken: 'asdf', expectedErrorMsg: 'Invalid token', }); }); }); });