Archived
2

added authorization

This commit is contained in:
VLE2FE 2020-04-23 13:59:45 +02:00
parent 90d34f1e1b
commit 1a3fdc567d
21 changed files with 393 additions and 47 deletions

View File

@ -3,7 +3,7 @@
- $ref: 'oas.yaml#/components/parameters/Id'
get:
summary: TODO condition by id
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /condition
responses:
@ -23,9 +23,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change condition
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /condition
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -51,9 +53,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete condition
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /condition
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'

View File

@ -3,7 +3,7 @@
- $ref: 'oas.yaml#/components/parameters/Id'
get:
summary: TODO get material details
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /material
responses:
@ -21,9 +21,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change material
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /material
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -47,9 +49,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete material
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /material
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'

View File

@ -3,7 +3,7 @@
- $ref: 'oas.yaml#/components/parameters/Id'
get:
summary: TODO measurement values by id
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /measurement
responses:
@ -23,9 +23,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change measurement
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /measurement
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -51,9 +53,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete measurement
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /measurement
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'

View File

@ -3,7 +3,7 @@
- $ref: 'oas.yaml#/components/parameters/Name'
get:
summary: TODO get model data by name
description: 'levels: dev, admin'
description: 'Auth: all, levels: dev, admin'
tags:
- /model
responses:
@ -24,7 +24,7 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/replace model data by name
description: 'levels: dev, admin'
description: 'Auth: all, levels: dev, admin'
tags:
- /model
requestBody:
@ -50,9 +50,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete model data
description: 'levels: dev, admin'
description: 'Auth: basic, levels: dev, admin'
tags:
- /model
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'

View File

@ -6,7 +6,10 @@ info:
version: 1.0.0
description: |
This API gives access to the project database.<br>
Access is restricted. Authentication can be obtained with HTTP Basic Auth using username and password. Data access methods can also be accessed using an API key at the URL ending like ?key=xxx<br>
Access is restricted. Authentication can be obtained with HTTP Basic Auth using username and password.
Data access methods can also be accessed using an API key at the URL ending like ?key=xxx<br>
The description lists available authentication methods, also the locks of each method close correspondingly
if the entered authentication is allowed.<br><br>
There are a number of different user levels: <br>
<ul>
<li>read: read access to the samples database</li>

View File

@ -1,6 +1,7 @@
/:
get:
summary: Root method
description: 'Auth: none'
tags:
- /
security: []
@ -14,5 +15,29 @@
status:
type: string
example: 'API server up and running!'
500:
$ref: 'oas.yaml#/components/responses/500'
/authorized:
get:
summary: Checks authorization
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /
responses:
200:
description: Authorized
content:
application/json:
schema:
properties:
status:
type: string
example: 'Authorization successful'
method:
type: string
example: 'basic'
401:
$ref: 'oas.yaml#/components/responses/401'
500:
$ref: 'oas.yaml#/components/responses/500'

View File

@ -1,7 +1,7 @@
/samples:
get:
summary: TODO all samples in overview
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /sample
responses:
@ -20,7 +20,7 @@
- $ref: 'oas.yaml#/components/parameters/Id'
get:
summary: TODO sample details
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /sample
responses:
@ -40,9 +40,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change sample
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /sample
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -68,9 +70,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete sample
description: 'levels: write, maintain, dev, admin'
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /sample
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'
@ -87,7 +91,7 @@
/sample/notes/fields:
get:
summary: TODO list all existing field names for custom notes fields
description: 'levels: write, maintain, dev, admin'
description: 'Auth: all, levels: write, maintain, dev, admin'
tags:
- /sample
responses:

View File

@ -1,7 +1,7 @@
/template/treatments:
get:
summary: TODO all available treatment methods
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /templates
security:
@ -30,7 +30,7 @@
- $ref: 'oas.yaml#/components/parameters/Name'
get:
summary: TODO treatment method details
description: 'levels: read, write, maintain, admin'
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /templates
security:
@ -59,9 +59,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change treatment method
description: 'levels: maintain, admin'
description: 'Auth: basic, levels: maintain, admin'
tags:
- /templates
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -101,9 +103,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete treatment method
description: 'levels: maintain, admin'
description: 'Auth: basic, levels: maintain, admin'
tags:
- /templates
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'
@ -120,7 +124,7 @@
/template/measurements:
get:
summary: TODO all available measurement methods
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /templates
security:
@ -150,7 +154,7 @@
- $ref: 'oas.yaml#/components/parameters/Name'
get:
summary: TODO measurement method details
description: 'levels: read, write, maintain, admin'
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /templates
security:
@ -180,9 +184,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO add/change measurement method
description: 'levels: maintain, admin'
description: 'Auth: basic, levels: maintain, admin'
tags:
- /templates
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -224,9 +230,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete measurement method
description: 'levels: maintain, admin'
description: 'Auth: basic, levels: maintain, admin'
tags:
- /templates
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'

View File

@ -1,7 +1,7 @@
/users:
get:
summary: TODO lists all users
description: 'levels: admin'
description: 'Auth: basic, levels: admin'
tags:
- /user
security:
@ -26,7 +26,7 @@
- $ref: 'oas.yaml#/components/parameters/Name'
get:
summary: TODO list user details
description: 'levels: read, write, maintain, dev get their own information without a name property specified, level: admin can get any user using the name parameter'
description: 'Auth: basic, levels: read, write, maintain, dev get their own information without a name property specified, level: admin can get any user using the name parameter'
tags:
- /user
security:
@ -52,9 +52,11 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO change user details
description: 'levels: read, write, maintain, dev can change their own information (except level) without a name property specified, level: admin can change any user using the name parameter'
description: 'Auth: basic, levels: read, write, maintain, dev can change their own information (except level) without a name property specified, level: admin can change any user using the name parameter'
tags:
- /user
security:
- BasicAuth: []
requestBody:
required: true
content:
@ -82,9 +84,11 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete user
description: 'levels: read, write, maintain, dev can delete their own account, level: admin can delete any user using the name parameter'
description: 'Auth: basic, levels: read, write, maintain, dev can delete their own account, level: admin can delete any user using the name parameter'
tags:
- /user
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'
@ -101,7 +105,7 @@
/user/key:
get:
summary: TODO get API key for the user
description: 'levels: read, write, maintain, dev, admin'
description: 'Auth: basic, levels: read, write, maintain, dev, admin'
tags:
- /user
security:
@ -120,7 +124,7 @@
/user/new:
post:
summary: TODO add new user
description: 'levels: admin'
description: 'Auth: basic, levels: admin'
tags:
- /user
security:
@ -157,6 +161,7 @@
/user/passreset:
post:
summary: TODO reset password and send mail to restore
description: 'Auth: none'
tags:
- /user
security: []

18
package-lock.json generated
View File

@ -208,6 +208,14 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@ -562,6 +570,11 @@
"safe-buffer": "5.1.2"
}
},
"content-filter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/content-filter/-/content-filter-1.1.2.tgz",
"integrity": "sha512-VaZ4Y7h776r0v2WxWqu3iatjYI6/N0msXK8O1ymtkFWbSvaFoCePksS8U60BS6dUMZeAlqhN09SuM7ghdzRP1Q=="
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
@ -1440,6 +1453,11 @@
}
}
},
"mongo-sanitize": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mongo-sanitize/-/mongo-sanitize-1.1.0.tgz",
"integrity": "sha512-6gB9AiJD+om2eZLxaPKIP5Q8P3Fr+s+17rVWso7hU0+MAzmIvIMlgTYuyvalDLTtE/p0gczcvJ8A3pbN1XmQ/A=="
},
"mongodb": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz",

View File

@ -4,6 +4,7 @@
"description": "API for the digital fingerprint of plastics mongodb",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"test": "mocha dist/**/**.spec.js",
"start": "tsc && node dist/index.js",
"dev": "nodemon -e ts,yaml --exec \"npm run start\""
@ -16,11 +17,14 @@
"@hapi/joi": "^17.1.1",
"@types/mocha": "^5.2.7",
"@types/node": "^13.1.6",
"basic-auth": "^2.0.1",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"cfenv": "^1.2.2",
"content-filter": "^1.1.2",
"express": "^4.17.1",
"json-schema": "^0.2.5",
"mongo-sanitize": "^1.1.0",
"mongoose": "^5.8.7",
"nodemon": "^2.0.3",
"swagger-ui-express": "^4.1.2",

View File

@ -39,6 +39,17 @@ export default class db {
if (err) done(err);
});
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
mongoose.connection.on('disconnected', () => { // reset state on disconnect
console.log('Database disconnected');
this.state.db = 0;
done();
});
process.on('SIGINT', () => { // close connection when app is terminated
mongoose.connection.close(() => {
console.log('Mongoose default connection disconnected through app termination');
process.exit(0);
});
});
mongoose.connection.once('open', () => {
console.log(process.env.NODE_ENV === 'test' ? '' : `Connected to ${connectionString}`);
this.state.db = mongoose.connection;

View File

@ -1,5 +1,5 @@
const globals = {
levels: [
levels: [ // access levels
'read',
'write',
'maintain',

100
src/helpers/authorize.ts Normal file
View File

@ -0,0 +1,100 @@
import basicAuth from 'basic-auth';
import bcrypt from 'bcryptjs';
import UserModel from '../models/user';
// appends req.auth(res, ['levels'], method = 'all')
// which returns sends error message and returns false if unauthorized, otherwise true
// req.authDetails returns eg. {methods: ['basic'], username: 'johndoe', level: 'write'}
module.exports = async (req, res, next) => {
let givenMethod = ''; // authorization method given by client, basic taken preferred
let user = {name: '', level: ''}; // user object
// test authentications
const userBasic = await basic(req, next);
if (userBasic) { // basic available
givenMethod = 'basic';
user = userBasic;
}
else { // if basic not available, test key
const userKey = await key(req, next);
if (userKey) {
givenMethod = 'key';
user = userKey;
}
}
req.auth = (res, levels, method = 'all') => {
if (givenMethod === method || (method === 'all' && givenMethod !== '')) { // method is available
if (levels.indexOf(user.level) > -1) { // level is available
return true;
}
else {
res.status(403).json({status: 'Forbidden'});
return false;
}
}
else {
res.status(401).json({status: 'Unauthorized'});
return false;
}
}
req.authDetails = {
method: givenMethod,
username: user.name,
level: user.level
};
next();
}
function basic (req, next): any { // checks basic auth and returns changed user object
return new Promise(resolve => {
const auth = basicAuth(req);
if (auth !== undefined) { // basic auth available
UserModel.find({name: auth.name}).lean().exec( 'find', (err, data) => { // find user
if (err) next(err);
if (data.length === 1) { // one user found
bcrypt.compare(auth.pass, data[0].pass, (err, res) => { // check password
if (err) next(err);
if (res === true) {
resolve({level: data[0].level, name: data[0].name});
}
else {
resolve(null);
}
});
}
else {
resolve(null);
}
});
}
else {
resolve(null);
}
});
}
function key (req, next): any { // checks API key and returns changed user object
return new Promise(resolve => {
if (req.query.key !== undefined) {
UserModel.find({key: req.query.key}).lean().exec( 'find', (err, data) => { // find user
if (err) next(err);
if (data.length === 1) { // one user found
resolve({level: data[0].level, name: data[0].name});
}
else {
resolve(null);
}
});
}
else {
resolve(null);
}
});
}

View File

@ -2,6 +2,8 @@ import express from 'express';
import bodyParser from 'body-parser';
import swagger from 'swagger-ui-express';
import jsonRefParser, {JSONSchema} from '@apidevtools/json-schema-ref-parser';
import contentFilter from 'content-filter';
import mongoSanitize from 'mongo-sanitize';
import db from './db';
@ -23,9 +25,23 @@ const port = process.env.PORT || 3000;
app.use(express.json({ limit: '5mb'}));
app.use(express.urlencoded({ extended: false, limit: '5mb' }));
app.use(bodyParser.json());
app.use(contentFilter()); // filter URL query attacks
app.use((req, res, next) => { // filter body query attacks
req.body = mongoSanitize(req.body);
next();
});
app.use((err, req, res, ignore) => { // bodyParser error handling
res.status(400).send({status: 'Invalid JSON body'});
});
app.use((req, res, next) => { // no database connection error
if (db.getState().db) {
next();
}
else {
res.status(500).send({status: 'Internal server error'});
}
});
app.use(require('./helpers/authorize')); // handle authentication
// require routes
app.use('/', require('./routes/root'));

View File

@ -26,14 +26,16 @@ describe('/', () => {
supertest(server)
.get('/')
.expect('Content-type', /json/)
.expect(200, (err, res) => {
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'API server up and running!'});
done();
});
});
});
describe('Testing unknown routes', () => {
describe('Unknown routes', () => {
let server;
before(done => {
@ -50,10 +52,94 @@ describe('Testing unknown routes', () => {
afterEach(done => {
server.close(done);
});
it('returns a 404 message', done => {
it('return a 404 message', done => {
supertest(server)
.get('/unknownroute')
.expect(404);
done();
.expect(404)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Not found'});
done();
});
});
});
describe('An unauthorized request', () => {
let server;
before(done => {
db.connect('test', done);
});
beforeEach(done => {
delete require.cache[require.resolve('../index')]; // prevent loading from cache
server = require('../index');
db.drop(err => { // reset database
if (err) return done(err);
db.loadJson(require('../test/db.json'), done);
});
});
afterEach(done => {
server.close(done);
});
it('returns a 401 message', done => {
supertest(server)
.get('/authorized')
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
it('does not work with correct username', done => {
supertest(server)
.get('/authorized')
.auth('admin', 'Abc123!!')
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
});
describe('An authorized request', () => {
let server;
before(done => {
db.connect('test', done);
});
beforeEach(done => {
delete require.cache[require.resolve('../index')]; // prevent loading from cache
server = require('../index');
db.drop(err => { // reset database
if (err) return done(err);
db.loadJson(require('../test/db.json'), done);
});
});
afterEach(done => {
server.close(done);
});
it('works with an API key', done => {
supertest(server)
.get('/authorized?key=5ea131671feb9c2ee0aafc9a')
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Authorization successful', method: 'key'});
done();
});
});
it('works with basic auth', done => {
supertest(server)
.get('/authorized')
.auth('admin', 'Abc123!#')
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Authorization successful', method: 'basic'});
done();
});
});
});

View File

@ -1,4 +1,5 @@
import express from 'express';
import globals from '../globals';
const router = express.Router();
@ -6,4 +7,9 @@ router.get('/', (req, res) => {
res.json({status: 'API server up and running!'});
});
router.get('/authorized', (req, res) => {
if (!req.auth(res, globals.levels)) return;
res.json({status: 'Authorization successful', method: req.authDetails.method});
});
module.exports = router;

View File

@ -26,10 +26,12 @@ describe('/user/new', () => {
it('returns the added user data', done => {
supertest(server)
.post('/user/new')
.auth('admin', 'Abc123!#')
.send({email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect('Content-type', /json/)
.expect(200, (err, res) => {
if (err) return done(err);
.expect(200)
.end((err, res) => {
if (err) done (err);
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');
@ -42,9 +44,11 @@ describe('/user/new', () => {
it('stores the data', done => {
supertest(server)
.post('/user/new')
.auth('admin', 'Abc123!#')
.send({email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect(200, err => {
if (err) return done(err);
.expect(200)
.end(err => {
if (err) done (err);
UserModel.find({name: 'johndoe'}).lean().exec( 'find', (err, data) => {
if (err) return done(err);
should(data).have.lengthOf(1);
@ -63,9 +67,11 @@ describe('/user/new', () => {
it('rejects a username already in use', done => {
supertest(server)
.post('/user/new')
.auth('admin', 'Abc123!#')
.send({email: 'j.doe@bosch.com', name: 'janedoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect(400, (err, res) => {
if (err) return done(err);
.expect(400)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Username already taken'});
UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => {
if (err) return done(err);
@ -73,5 +79,30 @@ describe('/user/new', () => {
done();
});
});
}); // TODO: authentication
});
it('rejects requests from non-admins', done => {
supertest(server)
.post('/user/new')
.auth('janedoe', 'Abc123!#')
.send({email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect('Content-type', /json/)
.expect(403)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Forbidden'});
done();
});
});
it('rejects requests from an admin API key', done => {
supertest(server)
.post('/user/new?key=5ea131671feb9c2ee0aafc9a')
.send({email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
});

View File

@ -11,6 +11,9 @@ router.get('/users', (req, res) => {
});
router.post('/user/new', (req, res, next) => {
console.log(req.authDetails);
if (!req.auth(res, ['admin'], 'basic')) return;
// validate input
const {error, value: user} = UserValidate.input(req.body);
if(error !== undefined) {

View File

@ -1,5 +1,5 @@
import joi from '@hapi/joi';
import globals from "../../globals";
import globals from '../../globals';
export default class UserValidate { // validate input for user
static input (data) {
@ -27,6 +27,7 @@ export default class UserValidate { // validate input for user
.required(),
device_name: joi.string()
.allow('')
.required()
}).validate(data);
}

View File

@ -5,12 +5,23 @@
"_id": "5ea0450ed851c30a90e70894",
"email": "jane.doe@bosch.com",
"name": "janedoe",
"pass": "$2a$10$KDKZjCsgDXwhtKdXZ9oG2ueDuCZsRKOMSqHuBfCM/2R0V6DRns.sy",
"pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
"level": "write",
"location": "Rng",
"device_name": "Alpha I",
"key": "5ea0450ed851c30a90e70899",
"__v": 0
},
{
"_id": "5ea131671feb9c2ee0aafc9b",
"email": "a.d.m.i.n@bosch.com",
"name": "admin",
"pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
"level": "admin",
"location": "Rng",
"device_name": "",
"key": "5ea131671feb9c2ee0aafc9a",
"__v": "0"
}
]
}