diff --git a/oas/condition.yaml b/oas/condition.yaml
index 1259ec1..cca8ca6 100644
--- a/oas/condition.yaml
+++ b/oas/condition.yaml
@@ -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'
diff --git a/oas/material.yaml b/oas/material.yaml
index 2ba26d7..d5d7d34 100644
--- a/oas/material.yaml
+++ b/oas/material.yaml
@@ -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'
diff --git a/oas/measurement.yaml b/oas/measurement.yaml
index 52c0430..0b4d5b2 100644
--- a/oas/measurement.yaml
+++ b/oas/measurement.yaml
@@ -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'
diff --git a/oas/model.yaml b/oas/model.yaml
index ce237e2..24df9af 100644
--- a/oas/model.yaml
+++ b/oas/model.yaml
@@ -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'
diff --git a/oas/oas.yaml b/oas/oas.yaml
index ba1bafd..03549c1 100644
--- a/oas/oas.yaml
+++ b/oas/oas.yaml
@@ -6,7 +6,10 @@ info:
version: 1.0.0
description: |
This API gives access to the project database.
- 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
+ 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
+ The description lists available authentication methods, also the locks of each method close correspondingly
+ if the entered authentication is allowed.
There are a number of different user levels:
- read: read access to the samples database
diff --git a/oas/others.yaml b/oas/others.yaml
index e5f200e..c543797 100644
--- a/oas/others.yaml
+++ b/oas/others.yaml
@@ -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'
\ No newline at end of file
diff --git a/oas/sample.yaml b/oas/sample.yaml
index 8464e06..b84be19 100644
--- a/oas/sample.yaml
+++ b/oas/sample.yaml
@@ -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:
diff --git a/oas/template.yaml b/oas/template.yaml
index bce58d0..a09cb21 100644
--- a/oas/template.yaml
+++ b/oas/template.yaml
@@ -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'
diff --git a/oas/user.yaml b/oas/user.yaml
index f8434f5..3db2b3c 100644
--- a/oas/user.yaml
+++ b/oas/user.yaml
@@ -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: []
diff --git a/package-lock.json b/package-lock.json
index 956d6f9..795376c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 234bb65..7630a0c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/db.ts b/src/db.ts
index 98c8617..de45a74 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -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;
diff --git a/src/globals.ts b/src/globals.ts
index e6db442..0d4ccdb 100644
--- a/src/globals.ts
+++ b/src/globals.ts
@@ -1,5 +1,5 @@
const globals = {
- levels: [
+ levels: [ // access levels
'read',
'write',
'maintain',
diff --git a/src/helpers/authorize.ts b/src/helpers/authorize.ts
new file mode 100644
index 0000000..e42f388
--- /dev/null
+++ b/src/helpers/authorize.ts
@@ -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);
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index cfaf696..67e29e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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'));
diff --git a/src/routes/root.spec.ts b/src/routes/root.spec.ts
index 276f159..61544a8 100644
--- a/src/routes/root.spec.ts
+++ b/src/routes/root.spec.ts
@@ -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();
+ });
});
});
\ No newline at end of file
diff --git a/src/routes/root.ts b/src/routes/root.ts
index bcbb40b..2705280 100644
--- a/src/routes/root.ts
+++ b/src/routes/root.ts
@@ -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;
diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts
index 4c50a70..f78d387 100644
--- a/src/routes/user.spec.ts
+++ b/src/routes/user.spec.ts
@@ -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();
+ });
+ });
});
\ No newline at end of file
diff --git a/src/routes/user.ts b/src/routes/user.ts
index 4c1d8ed..ffaebc7 100644
--- a/src/routes/user.ts
+++ b/src/routes/user.ts
@@ -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) {
diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts
index 8f658f3..1cccf41 100644
--- a/src/routes/validate/user.ts
+++ b/src/routes/validate/user.ts
@@ -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);
}
diff --git a/src/test/db.json b/src/test/db.json
index ba55d6f..7e32395 100644
--- a/src/test/db.json
+++ b/src/test/db.json
@@ -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"
}
]
}