diff --git a/.idea/dictionaries/VLE2FE.xml b/.idea/dictionaries/VLE2FE.xml index 62d7fb4..fecab46 100644 --- a/.idea/dictionaries/VLE2FE.xml +++ b/.idea/dictionaries/VLE2FE.xml @@ -18,12 +18,14 @@ colordesignationsuppl contentin crastin + customerold definma dfopdb dosiergeschw dpts einspritzgeschw errback + fmodel frameguard frankland functionlink diff --git a/api/model.yaml b/api/model.yaml index a025ac9..0f45f2b 100644 --- a/api/model.yaml +++ b/api/model.yaml @@ -1,7 +1,7 @@ /model/groups: get: - summary: list all available groups - description: 'Auth: basic, levels: read, write, dev, admin' + summary: list all available groups for user + description: 'Auth: basic, levels: predict, read, write, dev, admin' tags: - /model responses: @@ -137,5 +137,32 @@ $ref: 'api.yaml#/components/responses/403' 404: $ref: 'api.yaml#/components/responses/404' + 500: + $ref: 'api.yaml#/components/responses/500' + +/model/authorized/{url}: + parameters: + - name: url + in: path + required: true + description: URLComponent encoded URL of the requested model + schema: + type: string + example: https%3A%2F%2Fdefinma-model-test.apps.de1.bosch-iot-cloud.com%2Fpredict%2FH2O_A3WG6 + get: + summary: check user permissions for model URL + description: 'Auth: basic, levels: predict, read, write, dev, admin. dev and admin users can access all models, + other users need the model specified by an admin in user.models' + tags: + - /model + security: + - BasicAuth: [] + responses: + 200: + $ref: 'api.yaml#/components/responses/Ok' + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' 500: $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/api/schemas.yaml b/api/schemas.yaml index 75fa2f9..a61a819 100644 --- a/api/schemas.yaml +++ b/api/schemas.yaml @@ -213,8 +213,16 @@ User: items: type: string example: Alpha II + models: + type: array + description: _ids of allowed models + items: + type: string + example: 5ea0450ed851c30a90e70894 ModelItem: + allOf: + - $ref: 'api.yaml#/components/schemas/_Id' properties: name: type: string diff --git a/api/user.yaml b/api/user.yaml index 8b1448b..fba0192 100644 --- a/api/user.yaml +++ b/api/user.yaml @@ -14,7 +14,13 @@ schema: type: array items: - $ref: 'api.yaml#/components/schemas/User' + allOf: + - $ref: 'api.yaml#/components/schemas/User' + properties: + status: + type: string + description: can be deleted/new + example: new 401: $ref: 'api.yaml#/components/responses/401' 403: @@ -24,7 +30,7 @@ /user: get: summary: list own user details - description: 'Auth: basic, levels: read, write, dev, admin' + description: 'Auth: basic, levels: predict, read, write, dev, admin' tags: - /user security: @@ -44,7 +50,7 @@ $ref: 'api.yaml#/components/responses/500' put: summary: change user details - description: 'Auth: basic, levels: read, write, dev, admin' + description: 'Auth: basic, levels: predict, read, write, dev, admin' tags: - /user security: @@ -88,7 +94,7 @@ $ref: 'api.yaml#/components/responses/500' delete: summary: delete user - description: 'Auth: basic, levels: read, write, dev, admin' + description: 'Auth: basic, levels: predict, read, write, dev, admin' tags: - /user security: @@ -173,6 +179,25 @@ $ref: 'api.yaml#/components/responses/404' 500: $ref: 'api.yaml#/components/responses/500' +/user/restore/{name}: + put: + summary: restore deleted user + description: 'Auth: basic, levels: admin' + tags: + - /user + security: + - BasicAuth: [] + responses: + 200: + $ref: 'api.yaml#/components/responses/Ok' + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' + 404: + $ref: 'api.yaml#/components/responses/404' + 500: + $ref: 'api.yaml#/components/responses/500' /user/key: get: summary: get API key for the user diff --git a/data_import/spectrum-fix.js b/data_import/spectrum-fix.js index 2cf38b6..84c21af 100644 --- a/data_import/spectrum-fix.js +++ b/data_import/spectrum-fix.js @@ -1,8 +1,8 @@ const axios = require('axios'); -const host = 'http://localhost:3000'; -// const host = 'https://definma-api.apps.de1.bosch-iot-cloud.com'; +// const host = 'http://localhost:3000'; +const host = 'https://definma-api.apps.de1.bosch-iot-cloud.com'; let errors = []; @@ -22,7 +22,7 @@ async function fix() { }); console.log(res.data); // for all samples - for (let sampleIndex in res.data) { + for (let sampleIndex = 2000; sampleIndex < res.data.length; sampleIndex++) { console.log(`SAMPLE ${sampleIndex}/${res.data.length}`); const measurements = await axios({ // get all measurements method: 'get', @@ -42,7 +42,7 @@ async function fix() { for (let measurementIndex in measurements.data) { console.log(`${measurementIndex}/${measurements.data.length}`); const measurement = measurements.data[measurementIndex]; - if (measurement.values.hasOwnProperty('dpt')) { // is spectrum + if (measurement.values.hasOwnProperty('dpt') && measurement.values.dpt) { // is spectrum await axios({ // get all measurements method: 'put', url: host + '/measurement/' + measurement._id, diff --git a/src/globals.ts b/src/globals.ts index d1f3d85..037f25f 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -3,6 +3,7 @@ const globals = { levels: { // access levels, sorted asc by rights + predict: 'predict', read: 'read', write: 'write', dev: 'dev', diff --git a/src/helpers/authorize.ts b/src/helpers/authorize.ts index bfd6bd3..49b9278 100644 --- a/src/helpers/authorize.ts +++ b/src/helpers/authorize.ts @@ -10,7 +10,7 @@ import globals from '../globals'; module.exports = async (req, res, next) => { let givenMethod = ''; // authorization method given by client, basic taken preferred - let user = {name: '', level: '', id: '', location: ''}; // user object + let user = {name: '', level: '', id: '', location: '', models: []}; // user object // test authentications const userBasic = await basic(req, next); @@ -48,7 +48,8 @@ module.exports = async (req, res, next) => { username: user.name, level: user.level, id: user.id, - location: user.location + location: user.location, + models: user.models }; next(); @@ -59,7 +60,7 @@ function basic (req, next): any { // checks basic auth and returns changed user return new Promise(resolve => { const auth = basicAuth(req); if (auth !== undefined) { // basic auth available - UserModel.find({name: auth.name}).lean().exec( (err, data: any) => { // find user + UserModel.find({name: auth.name, status: globals.status.new}).lean().exec( (err, data: any) => { // find user if (err) return next(err); if (data.length === 1) { // one user found bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password @@ -69,7 +70,8 @@ function basic (req, next): any { // checks basic auth and returns changed user level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0], name: data[0].name, id: data[0]._id.toString(), - location: data[0].location + location: data[0].location, + models: data[0].models }); } else { @@ -91,14 +93,15 @@ function basic (req, next): any { // checks basic auth and returns changed user function key (req, next): any { // checks API key and returns changed user object return new Promise(resolve => { if (req.query.key !== undefined) { // key available - UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => { // find user + UserModel.find({key: req.query.key, status: globals.status.new}).lean().exec( (err, data: any) => { // find user if (err) return next(err); if (data.length === 1) { // one user found resolve({ level: Object.entries(globals.levels).find(e => e[1] === data[0].level)[0], name: data[0].name, id: data[0]._id.toString(), - location: data[0].location + location: data[0].location, + models: data[0].models }); if (!/^\/api/m.test(req.url)){ delete req.query.key; // delete query parameter to avoid interference with later validation diff --git a/src/models/model.ts b/src/models/model.ts index 92d555d..393b955 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -7,7 +7,7 @@ const ModelSchema = new mongoose.Schema({ name: String, url: String, label: String - } ,{ _id : false })] + } ,{ _id : true })] }); // changelog query helper diff --git a/src/models/user.ts b/src/models/user.ts index e1c8099..b83a1e2 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -1,5 +1,6 @@ import mongoose from 'mongoose'; import db from '../db'; +import ModelModel from './model'; const UserSchema = new mongoose.Schema({ name: {type: String, index: {unique: true}}, @@ -8,7 +9,9 @@ const UserSchema = new mongoose.Schema({ key: String, level: String, location: String, - devices: [String] + devices: [String], + models: [{type: mongoose.Schema.Types.ObjectId, ref: ModelModel}], + status: String }); // changelog query helper diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index 8c5e1ea..ec8885f 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -17,7 +17,7 @@ describe('/measurement', () => { url: '/measurement/800000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I'}, measurement_template: '300000000000000000000001'} + res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'} }); }); it('returns the measurement for an API key', done => { @@ -26,7 +26,7 @@ describe('/measurement', () => { url: '/measurement/800000000000000000000001', auth: {key: 'admin'}, httpStatus: 200, - res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I'}, measurement_template: '300000000000000000000001'} + res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'} }); }); it('filters out spectral data for a write user', done => { @@ -35,7 +35,7 @@ describe('/measurement', () => { url: '/measurement/800000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {device: 'Alpha I'}, measurement_template: '300000000000000000000001'} + res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'} }); }); it('returns deleted measurements for a dev/admin user', done => { @@ -88,7 +88,7 @@ describe('/measurement', () => { auth: {basic: 'admin'}, httpStatus: 200, req: {}, - res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I'}, measurement_template: '300000000000000000000001'} + res: {_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'} }); }); it('keeps unchanged values', done => { @@ -97,10 +97,10 @@ describe('/measurement', () => { url: '/measurement/800000000000000000000001', auth: {basic: 'admin'}, httpStatus: 200, - req: {values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I'}} + req: {values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I', filename: '1_0.DPT'}} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I'}, measurement_template: '300000000000000000000001'}); + should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[3997.12558,98.00555],[3995.08519,98.03253],[3993.04480,98.02657]], device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'}); MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => { if (err) return done(err); should(data).have.property('status','validated'); @@ -134,7 +134,7 @@ describe('/measurement', () => { req: {values: {dpt: [[1,2],[3,4],[5,6]]}} }).end((err, res) => { if (err) return done(err); - should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]], device: 'Alpha I'}, measurement_template: '300000000000000000000001'}); + should(res.body).be.eql({_id: '800000000000000000000001', sample_id: '400000000000000000000001', values: {dpt: [[1,2],[3,4],[5,6]], device: 'Alpha I', filename: '1_0.DPT'}, measurement_template: '300000000000000000000001'}); MeasurementModel.findById('800000000000000000000001').lean().exec((err, data: any) => { should(data).have.only.keys('_id', 'sample_id', 'values', 'measurement_template', 'status', '__v'); should(data.sample_id.toString()).be.eql('400000000000000000000001'); @@ -152,14 +152,15 @@ describe('/measurement', () => { url: '/measurement/800000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {values: {dpt: [[1,2],[3,4],[5,6]], device: 'Alpha I'}}, + req: {values: {dpt: [[1,2],[3,4],[5,6]], device: 'Alpha I', filename: '1_0.DPT'}}, log: { collection: 'measurements', dataAdd: { measurement_template: '300000000000000000000001', sample_id: '400000000000000000000001', status: 'new' - } + }, + dataIgn: ['values'] } }); }); diff --git a/src/routes/model.spec.ts b/src/routes/model.spec.ts index aefe226..dc75f5e 100644 --- a/src/routes/model.spec.ts +++ b/src/routes/model.spec.ts @@ -2,7 +2,6 @@ import should from 'should/as-function'; import ModelFileModel from '../models/model_file'; import TestHelper from "../test/helper"; import ModelModel from '../models/model'; -import _ from 'lodash'; describe('/model', () => { @@ -13,11 +12,11 @@ describe('/model', () => { after(done => TestHelper.after(done)); describe('GET /model/groups', () => { - it('returns all groups', done => { + it('returns all groups for an admin user', done => { TestHelper.request(server, done, { method: 'get', url: '/model/groups', - auth: {basic: 'janedoe'}, + auth: {basic: 'admin'}, httpStatus: 200, }).end((err, res) => { if (err) return done (err); @@ -27,7 +26,8 @@ describe('/model', () => { should(group).have.only.keys('group', 'models'); should(group).have.property('group').be.type('string'); should(group.models).matchEach(model => { - should(model).have.only.keys('name', 'url', 'label'); + should(model).have.only.keys('_id', 'name', 'url', 'label'); + should(model).have.property('_id').be.type('string'); should(model).have.property('name').be.type('string'); should(model).have.property('url').be.type('string'); should(model).have.property('label').be.type('string'); @@ -36,6 +36,23 @@ describe('/model', () => { done(); }); }); + it('returns all allowed groups for a predict user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/groups', + auth: {basic: 'customer'}, + httpStatus: 200, + }).end((err, res) => { + if (err) return done (err); + should(res.body).have.lengthOf(1); + should(res.body).matchEach(group => { + should(group).have.only.keys('group', 'models'); + should(group).have.property('group').be.type('string'); + should(group).have.property('models', [{_id: '120000000000000000000001', name: 'Model A', url: 'http://model-a.com', label: 'ml/g'}]); + }); + done(); + }); + }); it('rejects an API key', done => { TestHelper.request(server, done, { method: 'get', @@ -85,7 +102,12 @@ describe('/model', () => { should(res.body).be.eql({status: 'OK'}); ModelModel.findOne({group: 'classification'}).lean().exec((err, res) => { if (err) return done(err); - should(_.omit(res, ['_id', '__v'])).be.eql({group: 'classification', models: [{name: 'Model 0.1', url: 'http://model-0-1.com', label: 'group'}]}); + should(res).have.only.keys('_id', 'group', 'models', '__v'); + should(res).have.property('group', 'classification'); + should(res.models[0]).have.only.keys('_id', 'name', 'url', 'label'); + should(res.models[0]).have.property('name', 'Model 0.1'); + should(res.models[0]).have.property('url', 'http://model-0-1.com'); + should(res.models[0]).have.property('label', 'group'); done(); }); }); @@ -208,7 +230,12 @@ describe('/model', () => { should(res.body).be.eql({status: 'OK'}); ModelModel.findOne({group: 'VN'}).lean().exec((err, res) => { if (err) return done(err); - should(_.omit(res, ['_id'])).be.eql({group: 'VN', models: [{name: 'Model B', url: 'http://model-b.com', label: 'ml/g'}]}); + should(res).have.only.keys('_id', 'group', 'models'); + should(res).have.property('group', 'VN'); + should(res.models[0]).have.only.keys('_id', 'name', 'url', 'label'); + should(res.models[0]).have.property('name', 'Model B'); + should(res.models[0]).have.property('url', 'http://model-b.com'); + should(res.models[0]).have.property('label', 'ml/g'); done(); }); }); @@ -454,4 +481,48 @@ describe('/model', () => { }); }); }); + + describe('GET /model/authorized/{url}', () => { + it('returns OK for every model for admins', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/authorized/xx', + auth: {basic: 'admin'}, + httpStatus: 200, + res: {status: 'OK'} + }); + }); + it('returns OK for a specified model for a predict user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/authorized/http%3A%2F%2Fmodel-a.com', + auth: {basic: 'customer'}, + httpStatus: 200, + res: {status: 'OK'} + }); + }); + it('rejects a model not specified for a predict user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/authorized/http%3A%2F%2Fmodel-b.com', + auth: {basic: 'customer'}, + httpStatus: 403 + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/authorized/xx', + auth: {key: 'admin'}, + httpStatus: 401 + }); + }); + it('rejects an unauthorized request', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/model/authorized/http%3A%2F%2Fmodel-b.com', + httpStatus: 401 + }); + }); + }); }); \ No newline at end of file diff --git a/src/routes/model.ts b/src/routes/model.ts index 634f637..196d877 100644 --- a/src/routes/model.ts +++ b/src/routes/model.ts @@ -7,13 +7,21 @@ import _ from 'lodash'; import ModelValidate from './validate/model'; import res400 from './validate/res400'; import db from '../db'; +import mongoose from "mongoose"; const router = express.Router(); router.get('/model/groups', (req, res, next) => { - if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return; + if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return; - ModelModel.find().lean().exec((err, data) => { + let conditions: any = [{}, {}]; + if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights + conditions = [ + {'models._id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))}}, + {group: true, 'models.$': true} + ] + } + ModelModel.find(...conditions).lean().exec((err, data) => { if (err) return next(err); // validate all and filter null values from validation errors @@ -103,7 +111,7 @@ router.get('/model/file/:name', (req, res, next) => { }); }); -router.post('/model/file/:name', bodyParser.raw({limit: '5mb'}), (req, res, next) => { +router.post('/model/file/:name', bodyParser.raw({limit: '50mb'}), (req, res, next) => { if (!req.auth(res, ['dev', 'admin'], 'all')) return; ModelFileModel.replaceOne({name: req.params.name}, {name: req.params.name, data: req.body}).setOptions({upsert: true}) @@ -127,4 +135,26 @@ router.delete('/model/file/:name', (req, res, next) => { }); }); +router.get('/model/authorized/:url', (req, res, next) => { + if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return; + + if (['dev', 'admin'].indexOf(req.authDetails.level) < 0) { // if not dev or admin, user has to possess model rights + ModelModel.findOne({models: { $elemMatch: { + url: decodeURIComponent(req.params.url), + '_id': {$in: req.authDetails.models.map(e => mongoose.Types.ObjectId(e))} + }}}).lean().exec((err, data) => { + if (err) return next(err); + if (data) { + res.json({status: 'OK'}); + } + else { + res.status(403).json({status: 'Forbidden'}); + } + }); + } + else { + res.json({status: 'OK'}); + } +}); + module.exports = router; \ No newline at end of file diff --git a/src/routes/root.spec.ts b/src/routes/root.spec.ts index c195a3d..a397423 100644 --- a/src/routes/root.spec.ts +++ b/src/routes/root.spec.ts @@ -170,6 +170,14 @@ describe('/', () => { httpStatus: 401 }); }); + it('does not work with a deleted user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/authorized', + auth: {basic: {name: 'customerold', pass: 'Xyz890*)'}}, + httpStatus: 401 + }); + }); }); describe('An authorized request', () => { diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index bfff195..16107c9 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -632,6 +632,14 @@ describe('/sample', () => { res: {status: 'Invalid body format', details: '"status[0]" must be one of [validated, new]'} }); }); + it('rejects requests from a predict user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/samples', + auth: {basic: 'customer'}, + httpStatus: 403 + }); + }); it('rejects unauthorized requests', done => { TestHelper.request(server, done, { method: 'get', @@ -1789,7 +1797,7 @@ describe('/sample', () => { should(res.body).have.property('user_id', '000000000000000000000002'); should(res.body).have.property('status', 'new'); should(res.body).have.property('added').be.type('string'); - should(new Date().getTime() - new Date(res.body.added).getTime()).be.below(1000); + should(new Date().getTime() - new Date(res.body.added).getTime()).be.below(2000); done(); }); }); diff --git a/src/routes/sample.ts b/src/routes/sample.ts index 9262dec..4726bd3 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -499,9 +499,10 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => { } // do not execute check if condition is and was empty if (sample.hasOwnProperty('condition') && !(_.isEmpty(sample.condition) && _.isEmpty(sampleData.condition))) { - if (!await conditionCheck(sample.condition, 'change', res, next, + sample.condition = await conditionCheck(sample.condition, 'change', res, next, !(sampleData.condition.condition_template && - sampleData.condition.condition_template.toString() === sample.condition.condition_template))) return; + sampleData.condition.condition_template.toString() === sample.condition.condition_template)); + if (!sample.condition) return; } if (sample.hasOwnProperty('notes')) { @@ -627,7 +628,8 @@ router.post('/sample/new', async (req, res, next) => { } if (!_.isEmpty(sample.condition)) { // do not execute check if condition is empty - if (!await conditionCheck(sample.condition, 'change', res, next)) return; + sample.condition = await conditionCheck(sample.condition, 'change', res, next); + if (!sample.condition) return; } sample.status = globals.status.new; // set status to new @@ -646,6 +648,7 @@ router.post('/sample/new', async (req, res, next) => { sample.note_id = data._id; sample.user_id = req.authDetails.id; + console.log(sample); new SampleModel(sample).save((err, data) => { if (err) return next(err); db.log(req, 'samples', {_id: data._id}, data.toObject()); @@ -744,11 +747,11 @@ async function conditionCheck (condition, param, res, next, checkVersion = true) } // validate parameters - const {error, value: ignore} = + const {error, value} = ParametersValidate.input(_.omit(condition, 'condition_template'), conditionData.parameters, param); - console.log(error); if (error) {res400(error, res); return false;} - return conditionData; + value.condition_template = condition.condition_template; + return value; } function sampleRefCheck (sample, res, next) { // validate sample_references, resolves false for invalid reference diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts index 4a501e3..c63806b 100644 --- a/src/routes/user.spec.ts +++ b/src/routes/user.spec.ts @@ -23,7 +23,7 @@ describe('/user', () => { const json = require('../test/db.json'); should(res.body).have.lengthOf(json.collections.users.length); should(res.body).matchEach(user => { - should(user).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(user).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models', 'status'); should(user).have.property('_id').be.type('string'); should(user).have.property('email').be.type('string'); should(user).have.property('name').be.type('string'); @@ -32,6 +32,10 @@ describe('/user', () => { should(user.devices).matchEach(device => { should(device).be.type('string'); }); + should(user.models).matchEach(model => { + should(model).be.type('string'); + }); + should(user).have.property('status').be.type('string'); }); done(); }); @@ -70,13 +74,14 @@ describe('/user', () => { httpStatus: 200 }).end((err, res) => { if (err) return done (err); - should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models'); should(res.body).have.property('_id').be.type('string'); should(res.body).have.property('email', 'jane.doe@bosch.com'); should(res.body).have.property('name', 'janedoe'); should(res.body).have.property('level', 'write'); should(res.body).have.property('location', 'Rng'); should(res.body).have.property('devices', ['Alpha I']); + should(res.body).have.property('models', []); done(); }); }); @@ -88,13 +93,14 @@ describe('/user', () => { httpStatus: 200 }).end((err, res) => { if (err) return done (err); - should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models'); should(res.body).have.property('_id').be.type('string'); should(res.body).have.property('email', 'jane.doe@bosch.com'); should(res.body).have.property('name', 'janedoe'); should(res.body).have.property('level', 'write'); should(res.body).have.property('location', 'Rng'); should(res.body).have.property('devices', ['Alpha I']); + should(res.body).have.property('models', []); done(); }); }); @@ -149,13 +155,14 @@ describe('/user', () => { req: {} }).end((err, res) => { if (err) return done (err); - should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models'); should(res.body).have.property('_id').be.type('string'); should(res.body).have.property('email', 'jane.doe@bosch.com'); should(res.body).have.property('name', 'janedoe'); should(res.body).have.property('level', 'write'); should(res.body).have.property('location', 'Rng'); should(res.body).have.property('devices', ['Alpha I']); + should(res.body).have.property('models', []); done(); }); }); @@ -168,13 +175,14 @@ describe('/user', () => { req: {} }).end((err, res) => { if (err) return done (err); - should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models'); should(res.body).have.property('_id').be.type('string'); should(res.body).have.property('email', 'jane.doe@bosch.com'); should(res.body).have.property('name', 'janedoe'); should(res.body).have.property('level', 'write'); should(res.body).have.property('location', 'Rng'); should(res.body).have.property('devices', ['Alpha I']); + should(res.body).have.property('models', []); done(); }); }); @@ -184,13 +192,13 @@ describe('/user', () => { url: '/user', auth: {basic: 'admin'}, httpStatus: 200, - req: {name: 'adminnew', email: 'adminnew@bosch.com', pass: 'Abc123##', location: 'Abt', devices: ['test']} + req: {name: 'adminnew', email: 'adminnew@bosch.com', pass: 'Abc123##', location: 'Abt', devices: ['test'], models: ['120000000000000000000002']} }).end(err => { if (err) return done (err); UserModel.find({name: 'adminnew'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'devices', 'key', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'devices', 'models', 'key', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('name', 'adminnew'); should(data[0]).have.property('email', 'adminnew@bosch.com'); @@ -198,6 +206,8 @@ describe('/user', () => { should(data[0]).have.property('level', 'admin'); should(data[0]).have.property('location', 'Abt'); should(data[0]).have.property('devices', ['test']); + should(data[0].models[0].toString()).be.eql('120000000000000000000002'); + should(data[0]).have.property('status', 'new'); done(); }); }); @@ -250,6 +260,41 @@ describe('/user', () => { }); }); }); + it('lets the admin change accessible models', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/janedoe', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {models: ['120000000000000000000001']} + }).end(err => { + if (err) return done (err); + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { + if (err) return done(err); + should(data).have.lengthOf(1); + should(data[0].models[0].toString()).be.eql('120000000000000000000001'); + done(); + }); + }); + }); + it('does not change the models', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user', + auth: {basic: 'janedoe'}, + httpStatus: 400, default: false, + req: {models: ['120000000000000000000001']} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'Invalid body format', details: '"models" is not allowed'}); + UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { + if (err) return done(err); + should(data).have.lengthOf(1); + should(data[0]).have.property('models', []); + done(); + }); + }); + }); it('rejects a username already in use', done => { TestHelper.request(server, done, { method: 'put', @@ -273,7 +318,7 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 400, default: false, - req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']}, + req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: []}, res: {status: 'Username already taken'} }); }); @@ -354,7 +399,7 @@ describe('/user', () => { }); describe('DELETE /user/{name}', () => { - it('deletes own user details', done => { + it('sets own user details to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/user', @@ -365,12 +410,13 @@ describe('/user', () => { should(res.body).be.eql({status: 'OK'}); UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); - should(data).have.lengthOf(0); + should(data).have.lengthOf(1); + should(data[0]).have.property('status', 'deleted'); done(); }); }); }); - it('deletes other user details for admin', done => { + it('sets other user details to deleted for admin', done => { TestHelper.request(server, done, { method: 'delete', url: '/user/janedoe', @@ -381,7 +427,8 @@ describe('/user', () => { should(res.body).be.eql({status: 'OK'}); UserModel.find({name: 'janedoe'}).lean().exec( (err, data) => { if (err) return done(err); - should(data).have.lengthOf(0); + should(data).have.lengthOf(1); + should(data[0]).have.property('status', 'deleted'); done(); }); }); @@ -393,7 +440,8 @@ describe('/user', () => { auth: {basic: 'janedoe'}, httpStatus: 200, log: { - collection: 'users' + collection: 'users', + dataAdd: {status: 'deleted'} } }); }); @@ -438,6 +486,61 @@ describe('/user', () => { }); }); + describe('PUT /user/restore/{name}', () => { + it('sets the status', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/restore/customerold', + auth: {basic: 'admin'}, + httpStatus: 200, + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + UserModel.findOne({name: 'customerold'}).lean().exec((err, data: any) => { + if (err) return done(err); + should(data).have.property('status','new'); + done(); + }); + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/restore/customerold', + auth: {key: 'admin'}, + httpStatus: 401, + req: {} + }); + }); + it('rejects a write user', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/restore/customerold', + auth: {basic: 'janedoe'}, + httpStatus: 403, + req: {} + }); + }); + it('returns 404 for an unknown sample', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/restore/xxx', + auth: {basic: 'admin'}, + httpStatus: 404, + req: {} + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/user/restore/customerold', + httpStatus: 401, + req: {} + }); + }); + }); + describe('GET /user/key', () => { it('returns the right API key', done => { TestHelper.request(server, done, { @@ -472,16 +575,17 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 200, - req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']} + req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: ['120000000000000000000003']} }).end((err, res) => { if (err) return done (err); - should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices'); + should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'devices', 'models'); should(res.body).have.property('_id').be.type('string'); should(res.body).have.property('email', 'john.doe@bosch.com'); should(res.body).have.property('name', 'johndoe'); should(res.body).have.property('level', 'read'); should(res.body).have.property('location', 'Rng'); should(res.body).have.property('devices', ['Alpha II']); + should(res.body).have.property('models', ['120000000000000000000003']); done(); }); }); @@ -491,13 +595,13 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 200, - req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']} + req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: ['120000000000000000000002']} }).end(err => { if (err) return done (err); UserModel.find({name: 'johndoe'}).lean().exec( (err, data) => { if (err) return done(err); should(data).have.lengthOf(1); - should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'devices', 'key', '__v'); + should(data[0]).have.only.keys('_id', 'name', 'pass', 'email', 'level', 'location', 'devices', 'models', 'key', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('name', 'johndoe'); should(data[0]).have.property('email', 'john.doe@bosch.com'); @@ -505,6 +609,8 @@ describe('/user', () => { should(data[0]).have.property('level', 'read'); should(data[0]).have.property('location', 'Rng'); should(data[0]).have.property('devices', ['Alpha II']); + should(data[0].models.toString()).be.eql('120000000000000000000002'); + should(data[0]).have.property('status', 'new'); done(); }); }); @@ -515,10 +621,11 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 200, - req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']}, + req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: ['120000000000000000000002']}, log: { collection: 'users', - dataIgn: ['pass', 'key'] + dataIgn: ['pass', 'key'], + dataAdd: { status: 'new'} } }); }); @@ -528,7 +635,7 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 400, default: false, - req: {email: 'j.doe@bosch.com', name: 'janedoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']} + req: {email: 'j.doe@bosch.com', name: 'janedoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: []} }).end((err, res) => { if (err) return done (err); should(res.body).be.eql({status: 'Username already taken'}); @@ -545,7 +652,7 @@ describe('/user', () => { url: '/user/new', auth: {basic: 'admin'}, httpStatus: 400, default: false, - req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II']}, + req: {email: 'j.doe@bosch.com', name: 'passreset', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: []}, res: {status: 'Username already taken'} }); }); @@ -566,7 +673,7 @@ describe('/user', () => { auth: {basic: 'admin'}, httpStatus: 400, req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'xxx', location: 'Rng', devices: ['Alpha II']}, - res: {status: 'Invalid body format', details: '"level" must be one of [read, write, dev, admin]'} + res: {status: 'Invalid body format', details: '"level" must be one of [predict, read, write, dev, admin]'} }); }); it('rejects an invalid email address', done => { @@ -589,6 +696,16 @@ describe('/user', () => { res: {status: 'Invalid body format', details: '"pass" length must be at least 8 characters long'} }); }); + it('rejects an invalid model', done => { + TestHelper.request(server, done, { + method: 'post', + url: '/user/new', + auth: {basic: 'admin'}, + httpStatus: 400, + req: {email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', devices: ['Alpha II'], models: ['120000000000000000000001', '000000000000000000000001']}, + res: {status: 'Invalid model id'} + }); + }); it('rejects requests from non-admins', done => { TestHelper.request(server, done, { method: 'post', diff --git a/src/routes/user.ts b/src/routes/user.ts index 8c60eda..c7cfb7f 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -5,9 +5,11 @@ import _ from 'lodash'; import UserValidate from './validate/user'; import UserModel from '../models/user'; +import ModelModel from '../models/model'; import Mail from '../helpers/mail'; import res400 from './validate/res400'; import db from '../db'; +import globals from '../globals'; const router = express.Router(); @@ -17,14 +19,14 @@ router.get('/users', (req, res) => { UserModel.find({}).lean().exec( (err, data:any) => { // validate all and filter null values from validation errors - res.json(_.compact(data.map(e => UserValidate.output(e)))); + res.json(_.compact(data.map(e => UserValidate.output(e, 'admin')))); }); }); // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. // See https://forbeslindesay.github.io/express-route-tester/ for the generated regex router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { - if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return; + if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return; const username = getUsername(req, res); if (!username) return; @@ -40,8 +42,8 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { }); // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new -router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => { - if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return; +router.put('/user:username([/](?!key|new|restore).?*|/?)', async (req, res, next) => { + if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return; const username = getUsername(req, res); if (!username) return; @@ -59,6 +61,10 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => { if (!await usernameCheck(user.name, res, next)) return; } + if (user.hasOwnProperty('models')) { + if (!await modelsCheck(user.models, res, next)) return; + } + // get current mail address to compare to given address const oldUserData = await UserModel.findOne({name: username}).lean().exec().catch(err => next(err)); @@ -84,12 +90,12 @@ router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => { // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. // See https://forbeslindesay.github.io/express-route-tester/ for the generated regex router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { - if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return; + if (!req.auth(res, ['predict', 'read', 'write', 'dev', 'admin'], 'basic')) return; const username = getUsername(req, res); if (!username) return; - UserModel.findOneAndDelete({name: username}).log(req).lean().exec( (err, data:any) => { + UserModel.findOneAndUpdate({name: username}, {status: globals.status.del}).log(req).lean().exec( (err, data:any) => { if (err) return next(err); if (data) { res.json({status: 'OK'}) @@ -100,6 +106,20 @@ router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => { }); }); +router.put('/user/restore/:username', (req, res, next) => { + if (!req.auth(res, ['admin'], 'basic')) return; + + UserModel.findOneAndUpdate({name: req.params.username}, {status: globals.status.new}) + .log(req).lean().exec((err, data) => { + if (err) return next(err); + + if (!data) { + return res.status(404).json({status: 'Not found'}); + } + res.json({status: 'OK'}); + }); +}); + router.get('/user/key', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'basic')) return; @@ -114,12 +134,15 @@ router.post('/user/new', async (req, res, next) => { // validate input const {error, value: user} = UserValidate.input(req.body, 'new'); + console.log(error); if (error) return res400(error, res); // check that user does not already exist if (!await usernameCheck(user.name, res, next)) return; + if (!await modelsCheck(user.models, res, next)) return; user.key = mongoose.Types.ObjectId(); // use object id as unique API key + user.status = globals.status.new; bcrypt.hash(user.pass, 10, (err, hash) => { // password hashing user.pass = hash; new UserModel(user).save((err, data) => { // store user @@ -181,4 +204,18 @@ async function usernameCheck (name, res, next) { // check if username is alread return false; } return true; +} + +async function modelsCheck (models, res, next) { // check if model ids exist, returns false on error + let result = true; + for (let i in models) { + const model = await ModelModel.findOne({'models._id': mongoose.Types.ObjectId(models[i])}) + .lean().exec().catch(err => next(err)) as any; + if(!model) { + res.status(400).json({status: 'Invalid model id'}); + result = false; + break; + } + } + return result; } \ No newline at end of file diff --git a/src/routes/validate/model.ts b/src/routes/validate/model.ts index 30d3179..1a3fb2b 100644 --- a/src/routes/validate/model.ts +++ b/src/routes/validate/model.ts @@ -1,4 +1,5 @@ import Joi from 'joi'; +import IdValidate from './id'; export default class ModelValidate { // validate input for model @@ -29,9 +30,10 @@ export default class ModelValidate { // validate input for model } static output (data) { // validate output and strip unwanted properties, returns null if not valid + data = IdValidate.stringify(data); const {value, error} = Joi.object({ group: this.model.group, - models: Joi.array().items(this.model.model) + models: Joi.array().items(this.model.model.append({_id: IdValidate.get()})) }).validate(data, {stripUnknown: true}); return error !== undefined? null : value; } diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts index 1a0dff3..4ac78d4 100644 --- a/src/routes/validate/user.ts +++ b/src/routes/validate/user.ts @@ -31,7 +31,13 @@ export default class UserValidate { // validate input for user .items(Joi.string() .allow('') .max(128) - ) + ), + + models: Joi.array() + .items(IdValidate.get()), + + status: Joi.string() + .valid(...Object.values(globals.status)) }; private static specialUsernames: string[] = ['admin', 'user', 'key', 'new', 'passreset']; // names a user cannot take @@ -44,7 +50,8 @@ export default class UserValidate { // validate input for user pass: this.user.pass.required(), level: this.user.level.required(), location: this.user.location.required(), - devices: this.user.devices.required() + devices: this.user.devices.required(), + models: this.user.models.required() }).validate(data); } else if (param === 'change') { @@ -63,7 +70,8 @@ export default class UserValidate { // validate input for user pass: this.user.pass, level: this.user.level, location: this.user.location, - devices: this.user.devices + devices: this.user.devices, + models: this.user.models }).validate(data); } else { @@ -71,16 +79,21 @@ export default class UserValidate { // validate input for user } } - static output (data) { // validate output and strip unwanted properties, returns null if not valid + static output (data, param = '') { // validate output and strip unwanted properties, returns null if not valid data = IdValidate.stringify(data); - const {value, error} = Joi.object({ + const validate: {[key: string]: object} = { _id: IdValidate.get(), name: this.user.name, email: this.user.email, level: this.user.level, location: this.user.location, - devices: this.user.devices - }).validate(data, {stripUnknown: true}); + devices: this.user.devices, + models: this.user.models + } + if (param === 'admin') { + validate.status = this.user.status; + } + const {value, error} = Joi.object(validate).validate(data, {stripUnknown: true}); return error !== undefined? null : value; } diff --git a/src/test/db.json b/src/test/db.json index 8b59273..9cd244f 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -734,11 +734,13 @@ "group": "VN", "models": [ { + "_id": {"$oid":"120000000000000000000001"}, "name": "Model A", "url": "http://model-a.com", "label": "ml/g" }, { + "_id": {"$oid":"120000000000000000000002"}, "name": "Model B", "url": "http://model-b.com", "label": "ml/g" @@ -749,6 +751,7 @@ "group": "Moisture", "models": [ { + "_id": {"$oid":"120000000000000000000003"}, "name": "Model 1", "url": "http://model-1.com", "label": "weight %" @@ -765,7 +768,9 @@ "level": "read", "location": "Rng", "devices": ["Alpha I"], + "models": [], "key": "000000000000000000001001", + "status": "new", "__v": 0 }, { @@ -776,7 +781,9 @@ "level": "write", "location": "Rng", "devices": ["Alpha I"], + "models": [], "key": "000000000000000000001002", + "status": "new", "__v": 0 }, { @@ -787,7 +794,9 @@ "level": "admin", "location": "Rng", "devices": [""], + "models": [], "key": "000000000000000000001003", + "status": "new", "__v": "0" }, { @@ -798,7 +807,35 @@ "level": "write", "location": "Fe", "devices": ["Alpha I"], + "models": [], "key": "000000000000000000001004", + "status": "new", + "__v": 0 + }, + { + "_id": {"$oid":"000000000000000000000005"}, + "email": "customer@company.com", + "name": "customer", + "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi", + "level": "predict", + "location": "Fe", + "devices": [], + "models": [{"$oid":"120000000000000000000001"}], + "key": "000000000000000000001005", + "status": "new", + "__v": 0 + }, + { + "_id": {"$oid":"000000000000000000000006"}, + "email": "customerold@company2.com", + "name": "customerold", + "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi", + "level": "predict", + "location": "Fe", + "devices": [], + "models": [{"$oid":"120000000000000000000001"}], + "key": "000000000000000000001006", + "status": "deleted", "__v": 0 } ], diff --git a/src/test/helper.ts b/src/test/helper.ts index ff337a3..70dd638 100644 --- a/src/test/helper.ts +++ b/src/test/helper.ts @@ -11,7 +11,8 @@ export default class TestHelper { admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'}, janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'}, user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'}, - johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'} + johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'}, + customer: {pass: 'Xyz890*)', key: '000000000000000000001005', id: '000000000000000000000005'} } public static res = { // default responses