From b68d5d174d8f485e766865892816710a4794b836 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Fri, 3 Jan 2020 11:36:39 +0100 Subject: [PATCH] Adding /api/user/login integration test --- package-lock.json | 70 +++++++++++++++++++ package.json | 1 + .../middlewares/user/AuthenticationMWs.ts | 2 +- src/backend/model/database/sql/UserManager.ts | 13 +--- .../model/database/sql/enitites/UserEntity.ts | 2 +- src/backend/server.ts | 10 ++- src/common/DataStructureVersion.ts | 2 +- src/common/event/Event.ts | 48 +++++++++---- .../backend/integration/routers/UserRouter.ts | 49 +++++++++++++ 9 files changed, 168 insertions(+), 29 deletions(-) create mode 100644 test/backend/integration/routers/UserRouter.ts diff --git a/package-lock.json b/package-lock.json index c7558b8b..ab6e6a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3160,6 +3160,12 @@ "@types/keygrip": "*" } }, + "@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, "@types/csurf": { "version": "1.9.36", "resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.9.36.tgz", @@ -4053,6 +4059,16 @@ "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "dev": true }, + "@types/superagent": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, "@types/undertaker": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.2.tgz", @@ -5854,6 +5870,21 @@ "type-detect": "^4.0.5" } }, + "chai-http": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", + "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "dev": true, + "requires": { + "@types/chai": "4", + "@types/superagent": "^3.8.3", + "cookiejar": "^2.1.1", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.5.1", + "superagent": "^3.7.0" + } + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -6657,6 +6688,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, "cookies": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", @@ -8856,6 +8893,12 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -11076,6 +11119,15 @@ "is-extglob": "^2.1.1" } }, + "is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "dev": true, + "requires": { + "ip-regex": "^2.0.0" + } + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -17114,6 +17166,24 @@ "when": "~3.6.x" } }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index a2b22bbb..3a50aed0 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@yaga/leaflet-ng2": "1.0.0", "bootstrap": "4.4.1", "chai": "4.2.0", + "chai-http": "^4.3.0", "codelyzer": "5.2.0", "core-js": "3.4.7", "ejs-loader": "0.3.5", diff --git a/src/backend/middlewares/user/AuthenticationMWs.ts b/src/backend/middlewares/user/AuthenticationMWs.ts index 8f5158ac..8dee7b49 100644 --- a/src/backend/middlewares/user/AuthenticationMWs.ts +++ b/src/backend/middlewares/user/AuthenticationMWs.ts @@ -140,7 +140,7 @@ export class AuthenticationMWs { (typeof req.body.loginCredential === 'undefined') || (typeof req.body.loginCredential.username === 'undefined') || (typeof req.body.loginCredential.password === 'undefined')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR)); + return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'not all parameters are included, got' + JSON.stringify(req.body))); } try { // lets find the user diff --git a/src/backend/model/database/sql/UserManager.ts b/src/backend/model/database/sql/UserManager.ts index 028950ec..c98c06a0 100644 --- a/src/backend/model/database/sql/UserManager.ts +++ b/src/backend/model/database/sql/UserManager.ts @@ -17,9 +17,6 @@ export class UserManager implements IUserManager { delete filter.password; const user = (await connection.getRepository(UserEntity).findOne(filter)); - if (user.permissions) { - user.permissions = JSON.parse(user.permissions); - } if (pass && !PasswordHelper.comparePassword(pass, user.password)) { throw new Error('No entry found'); @@ -30,19 +27,11 @@ export class UserManager implements IUserManager { public async find(filter: any) { const connection = await SQLConnection.getConnection(); - return (await connection.getRepository(UserEntity).find(filter)).map(user => { - if (user.permissions) { - user.permissions = JSON.parse(user.permissions); - } - return user; - }); + return await connection.getRepository(UserEntity).find(filter); } public async createUser(user: UserDTO) { const connection = await SQLConnection.getConnection(); - if (user.permissions) { - user.permissions = JSON.stringify(user.permissions); - } user.password = PasswordHelper.cryptPassword(user.password); return connection.getRepository(UserEntity).save(user); } diff --git a/src/backend/model/database/sql/enitites/UserEntity.ts b/src/backend/model/database/sql/enitites/UserEntity.ts index 9eb20b16..dd0bc9ca 100644 --- a/src/backend/model/database/sql/enitites/UserEntity.ts +++ b/src/backend/model/database/sql/enitites/UserEntity.ts @@ -17,7 +17,7 @@ export class UserEntity implements UserDTO { @Column('smallint') role: UserRoles; - @Column('text', {nullable: true}) + @Column('simple-array', {nullable: true}) permissions: string[]; } diff --git a/src/backend/server.ts b/src/backend/server.ts index 2c5c788d..6484ad74 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -17,6 +17,7 @@ import {Router} from './routes/Router'; import {ServerConfig} from '../common/config/private/IPrivateConfig'; import {PhotoProcessing} from './model/fileprocessing/PhotoProcessing'; import * as _csrf from 'csurf'; +import {Event} from '../common/event/Event'; const _session = require('cookie-session'); @@ -26,6 +27,7 @@ const LOG_TAG = '[server]'; export class Server { + public onStarted = new Event(); private app: _express.Express; private server: HttpServer; @@ -36,6 +38,10 @@ export class Server { this.init().catch(console.error); } + get App(): any { + return this.server; + } + async init(): Promise { Logger.info(LOG_TAG, 'running diagnostics...'); await ConfigDiagnostics.runDiagnostics(); @@ -69,7 +75,7 @@ export class Server { // for parsing application/json this.app.use(_bodyParser.json()); this.app.use(cookieParser()); - this.app.use(_csrf({cookie: true})); + // this.app.use(_csrf({cookie: true})); DiskManager.init(); PhotoProcessing.init(); @@ -96,7 +102,7 @@ export class Server { this.server.on('error', this.onError); this.server.on('listening', this.onListening); - + this.onStarted.trigger(); } /** diff --git a/src/common/DataStructureVersion.ts b/src/common/DataStructureVersion.ts index 3767b7ce..9f2a44da 100644 --- a/src/common/DataStructureVersion.ts +++ b/src/common/DataStructureVersion.ts @@ -1 +1 @@ -export const DataStructureVersion = 14; +export const DataStructureVersion = 15; diff --git a/src/common/event/Event.ts b/src/common/event/Event.ts index 2630eb2a..a851dcba 100644 --- a/src/common/event/Event.ts +++ b/src/common/event/Event.ts @@ -1,30 +1,54 @@ -function isFunction(functionToCheck: any) { - const getType = {}; - return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; -} - export class Event { - private handlers: ((data?: T) => void)[] = []; + protected handlers: ((data?: T) => void)[] = []; + protected singleHandlers: ((data?: T) => void)[] = []; - public on(handler: (data?: T) => void) { - if (!isFunction(handler)) { - throw new Error('Handler is not a function'); + public on(handler: (data?: T) => void): void { + if (typeof (handler) !== 'function') { + throw new Error('Event::on: Handler is not a function'); } this.handlers.push(handler); } - public off(handler: (data?: T) => void) { + + public once(handler: (data?: T) => void): void { + if (typeof (handler) !== 'function') { + throw new Error('Event::once: Handler is not a function'); + } + this.singleHandlers.push(handler); + } + + public wait(): Promise { + return new Promise((resolve) => { + this.once(() => { + resolve(); + }); + }); + } + + public off(handler: (data?: T) => void): void { this.handlers = this.handlers.filter(h => h !== handler); + this.singleHandlers = this.singleHandlers.filter(h => h !== handler); } - public allOff() { + public allOff(): void { this.handlers = []; + this.singleHandlers = []; } - public trigger(data?: T) { + public trigger(data?: T): void { if (this.handlers) { this.handlers.slice(0).forEach(h => h(data)); } + if (this.singleHandlers) { + this.singleHandlers.slice(0).forEach(h => h(data)); + this.singleHandlers = []; + } + } + + public hasListener(): boolean { + return this.handlers.length !== 0 || this.singleHandlers.length !== 0; } } + + diff --git a/test/backend/integration/routers/UserRouter.ts b/test/backend/integration/routers/UserRouter.ts new file mode 100644 index 00000000..6447b3ff --- /dev/null +++ b/test/backend/integration/routers/UserRouter.ts @@ -0,0 +1,49 @@ +import {Server} from '../../../../src/backend/server'; +import {Config} from '../../../../src/common/config/private/Config'; +import {LoginCredential} from '../../../../src/common/entities/LoginCredential'; +import {UserDTO, UserRoles} from '../../../../src/common/entities/UserDTO'; +import * as path from 'path'; +import * as util from 'util'; +import * as rimraf from 'rimraf'; +import {ServerConfig} from '../../../../src/common/config/private/IPrivateConfig'; +import {SQLConnection} from '../../../../src/backend/model/database/sql/SQLConnection'; + +process.env.NODE_ENV = 'test'; +const chai: any = require('chai'); +const chaiHttp = require('chai-http'); +const should = chai.should(); +chai.use(chaiHttp); + +const rimrafPR = util.promisify(rimraf); +describe('UserRouter', () => { + + const tempDir = path.join(__dirname, '../../tmp'); + beforeEach(async () => { + await rimrafPR(tempDir); + Config.Server.Threading.enabled = false; + Config.Server.Database.type = ServerConfig.DatabaseType.sqlite; + Config.Server.Database.dbFolder = tempDir; + }); + + + afterEach(async () => { + await SQLConnection.close(); + await rimrafPR(tempDir); + }); + + describe('/GET login', () => { + it('it should GET all the books', async () => { + const srv = new Server(); + await srv.onStarted.wait(); + const result = await chai.request(srv.App) + .post('/api/user/login') + .send({loginCredential: {password: 'admin', username: 'admin', rememberMe: false}}); + + result.res.should.have.status(200); + result.body.should.be.a('object'); + should.equal(result.body.error, null); + result.body.result.should.deep.equal({id: 1, name: 'admin', role: UserRoles.Admin, permissions: null}); + + }); + }); +});