Archived
2

added GET /user route

This commit is contained in:
VLE2FE 2020-04-24 12:25:32 +02:00
parent 8bf408138f
commit a64229d1dc
9 changed files with 385 additions and 33 deletions

View File

@ -1,5 +1,6 @@
Id:
type: string
example: 5ea0450ed851c30a90e70894
_Id:
properties:
_id:

View File

@ -1,6 +1,6 @@
/users:
get:
summary: TODO lists all users
summary: lists all users
description: 'Auth: basic, levels: admin'
tags:
- /user
@ -21,12 +21,10 @@
$ref: 'oas.yaml#/components/responses/403'
500:
$ref: 'oas.yaml#/components/responses/500'
/user/{name}:
parameters:
- $ref: 'oas.yaml#/components/parameters/Name'
/user:
get:
summary: TODO list user details
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'
summary: list own user details
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /user
security:
@ -37,9 +35,7 @@
content:
application/json:
schema:
type: array
items:
$ref: 'oas.yaml#/components/schemas/User'
$ref: 'oas.yaml#/components/schemas/User'
400:
$ref: 'oas.yaml#/components/responses/400'
401:
@ -52,7 +48,98 @@
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO change user details
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'
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /user
security:
- BasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: 'oas.yaml#/components/schemas/_Id'
- $ref: 'oas.yaml#/components/schemas/UserName'
- $ref: 'oas.yaml#/components/schemas/Email'
properties:
pass:
type: string
writeOnly: true
example: Abc123!#
location:
type: string
example: Rng
device_name:
type: string
example: Alpha II
responses:
200:
description: user details
content:
application/json:
schema:
$ref: 'oas.yaml#/components/schemas/User'
400:
$ref: 'oas.yaml#/components/responses/400'
401:
$ref: 'oas.yaml#/components/responses/401'
403:
$ref: 'oas.yaml#/components/responses/403'
404:
$ref: 'oas.yaml#/components/responses/404'
500:
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete user
description: 'Auth: basic, levels: read, write, maintain, admin'
tags:
- /user
security:
- BasicAuth: []
responses:
200:
$ref: 'oas.yaml#/components/responses/Ok'
400:
$ref: 'oas.yaml#/components/responses/400'
401:
$ref: 'oas.yaml#/components/responses/401'
403:
$ref: 'oas.yaml#/components/responses/403'
404:
$ref: 'oas.yaml#/components/responses/404'
500:
$ref: 'oas.yaml#/components/responses/500'
/user/{name}:
parameters:
- $ref: 'oas.yaml#/components/parameters/Name'
get:
summary: list user details
description: 'Auth: basic, levels: admin'
tags:
- /user
security:
- BasicAuth: []
responses:
200:
description: user details
content:
application/json:
schema:
$ref: 'oas.yaml#/components/schemas/User'
400:
$ref: 'oas.yaml#/components/responses/400'
401:
$ref: 'oas.yaml#/components/responses/401'
403:
$ref: 'oas.yaml#/components/responses/403'
404:
$ref: 'oas.yaml#/components/responses/404'
500:
$ref: 'oas.yaml#/components/responses/500'
put:
summary: TODO change user details
description: 'Auth: basic, levels: admin'
tags:
- /user
security:
@ -69,9 +156,7 @@
content:
application/json:
schema:
type: array
items:
$ref: 'oas.yaml#/components/schemas/User'
$ref: 'oas.yaml#/components/schemas/User'
400:
$ref: 'oas.yaml#/components/responses/400'
401:
@ -84,7 +169,7 @@
$ref: 'oas.yaml#/components/responses/500'
delete:
summary: TODO delete user
description: 'Auth: basic, levels: read, write, maintain, dev can delete their own account, level: admin can delete any user using the name parameter'
description: 'Auth: basic, levels: admin'
tags:
- /user
security:
@ -123,7 +208,7 @@
$ref: 'oas.yaml#/components/responses/500'
/user/new:
post:
summary: TODO add new user
summary: add new user
description: 'Auth: basic, levels: admin'
tags:
- /user
@ -160,7 +245,7 @@
$ref: 'oas.yaml#/components/responses/500'
/user/passreset:
post:
summary: TODO reset password and send mail to restore
summary: reset password and send mail to restore
description: 'Auth: none'
tags:
- /user

View File

@ -35,7 +35,7 @@ export default class db {
}
// connect to db
mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true}, err => {
mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true}, err => {
if (err) done(err);
});
mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
@ -89,7 +89,6 @@ export default class db {
let loadCounter = 0; // count number of loaded collections to know when to return done()
Object.keys(json.collections).forEach(collectionName => { // create each collection
for(let i in json.collections[collectionName]) { // convert $oid fields to actual ObjectIds
console.log(json.collections[collectionName][i]);
Object.keys(json.collections[collectionName][i]).forEach(key => {
json.collections[collectionName][i][key] = json.collections[collectionName][i][key].hasOwnProperty('$oid') ? mongoose.Types.ObjectId(json.collections[collectionName][i][key].$oid) : json.collections[collectionName][i][key];
})

View File

@ -1,7 +1,7 @@
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema({
name: String,
name: {type: String, index: {unique: true}},
email: String,
pass: String,
key: String,

View File

@ -3,7 +3,7 @@ import should from 'should/as-function';
import db from '../db';
describe('/', () => {
describe('GET /', () => {
let server;
before(done => {

View File

@ -4,7 +4,258 @@ import db from '../db';
import UserModel from '../models/user';
describe('/user/new', () => {
describe('GET /users', () => {
let server;
before(done => {
process.env.port = '2999';
process.env.NODE_ENV = 'test';
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 all users', done => {
supertest(server)
.get('/users')
.auth('admin', 'Abc123!#')
.expect('Content-type', /json/)
.expect(200)
.end((err, res) => {
if (err) done (err);
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', 'device_name');
should(user).have.property('_id').be.type('string');
should(user).have.property('email').be.type('string');
should(user).have.property('name').be.type('string');
should(user).have.property('level').be.type('string');
should(user).have.property('location').be.type('string');
should(user).have.property('device_name').be.type('string');
});
done();
});
});
it('rejects requests from non-admins', done => {
supertest(server)
.get('/users')
.auth('janedoe', 'Xyz890*)')
.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)
.get('/users?key=5ea131671feb9c2ee0aafc9a')
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
});
describe('GET /user/{name}', () => {
let server;
before(done => {
process.env.port = '2999';
process.env.NODE_ENV = 'test';
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 own user details', done => {
supertest(server)
.get('/user')
.auth('janedoe', 'Xyz890*)')
.expect('Content-type', /json/)
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
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('device_name', 'Alpha I');
done();
});
});
it('returns other user details for admin', done => {
supertest(server)
.get('/user/janedoe')
.auth('admin', 'Abc123!#')
.expect('Content-type', /json/)
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
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('device_name', 'Alpha I');
done();
});
});
it('rejects requests from non-admins for another user', done => {
supertest(server)
.get('/user/admin')
.auth('janedoe', 'Xyz890*)')
.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 a user API key', done => {
supertest(server)
.get('/user?key=5ea0450ed851c30a90e70899')
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
it('rejects requests from an admin API key', done => {
supertest(server)
.get('/user/janedoe?key=5ea131671feb9c2ee0aafc9a')
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
});
describe('PUT /user/{name}', () => {
let server;
before(done => {
process.env.port = '2999';
process.env.NODE_ENV = 'test';
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 own user details', done => {
supertest(server)
.get('/user')
.auth('janedoe', 'Xyz890*)')
.expect('Content-type', /json/)
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
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('device_name', 'Alpha I');
done();
});
});
it('returns other user details for admin', done => {
supertest(server)
.get('/user/janedoe')
.auth('admin', 'Abc123!#')
.expect('Content-type', /json/)
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
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('device_name', 'Alpha I');
done();
});
});
it('rejects requests from non-admins for another user', done => {
supertest(server)
.get('/user/admin')
.auth('janedoe', 'Xyz890*)')
.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 a user API key', done => {
supertest(server)
.get('/user?key=5ea0450ed851c30a90e70899')
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
it('rejects requests from an admin API key', done => {
supertest(server)
.get('/user/janedoe?key=5ea131671feb9c2ee0aafc9a')
.expect('Content-type', /json/)
.expect(401)
.end((err, res) => {
if (err) done (err);
should(res.body).be.eql({status: 'Unauthorized'});
done();
});
});
});
describe('POST /user/new', () => {
let server;
before(done => {
@ -32,6 +283,7 @@ describe('/user/new', () => {
.expect(200)
.end((err, res) => {
if (err) done (err);
should(res.body).have.only.keys('_id', 'email', 'name', 'level', 'location', 'device_name');
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');
@ -83,7 +335,7 @@ describe('/user/new', () => {
it('rejects requests from non-admins', done => {
supertest(server)
.post('/user/new')
.auth('janedoe', 'Abc123!#')
.auth('janedoe', 'Xyz890*)')
.send({email: 'john.doe@bosch.com', name: 'johndoe', pass: 'Abc123!#', level: 'read', location: 'Rng', device_name: 'Alpha II'})
.expect('Content-type', /json/)
.expect(403)
@ -108,9 +360,7 @@ describe('/user/new', () => {
});
describe('/user/passreset', () => {
describe('POST /user/passreset', () => {
let server;
before(done => {
@ -191,7 +441,6 @@ describe('/user/passreset', () => {
should(res.body).be.eql({status: 'OK'});
UserModel.find({name: 'janedoe'}).lean().exec( (err, data: any) => {
if (err) return done(err);
console.log(data);
should(data[0].pass).not.eql(oldpass);
done();
});

View File

@ -8,7 +8,24 @@ import mail from '../helpers/mail';
const router = express.Router();
router.get('/users', (req, res) => {
res.json({message: 'users up and running!'});
if (!req.auth(res, ['admin'], 'basic')) return;
UserModel.find({}).lean().exec( (err, data:any) => {
res.json(data.map(e => UserValidate.output(e)).filter(e => e !== null)); // validate all and filter null values from validation errors
});
});
router.get('/user/:username*?', (req, res) => {
if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
let username = req.authDetails.username;
if (req.params.username !== undefined) {
if (!req.auth(res, ['admin'], 'basic')) return;
username = req.params.username;
}
UserModel.findOne({name: username}).lean().exec( (err, data:any) => {
res.json(UserValidate.output(data)); // validate all and filter null values from validation errors
});
});
router.post('/user/new', (req, res, next) => {
@ -22,7 +39,7 @@ router.post('/user/new', (req, res, next) => {
}
// check that user does not already exist
UserModel.find({name: user.name}).lean().exec( 'find', (err, data) => {
UserModel.find({name: user.name}).lean().exec( (err, data:any) => {
if (err) next(err);
if (data.length > 0) {
res.status(400).json({status: 'Username already taken'});

View File

@ -33,13 +33,14 @@ export default class UserValidate { // validate input for user
}
static output (data) { // validate output from database for needed properties, strip everything else
return joi.object({
const {value, error} = joi.object({
_id: joi.any(),
name: joi.string(),
email: joi.string(),
level: joi.string(),
location: joi.string(),
device_name: joi.string()
}).validate(data, {stripUnknown: true}).value;
device_name: joi.string().allow('')
}).validate(data, {stripUnknown: true})
return error !== undefined? null : value;
}
}

View File

@ -5,7 +5,7 @@
"_id": {"$oid":"5ea0450ed851c30a90e70894"},
"email": "jane.doe@bosch.com",
"name": "janedoe",
"pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
"pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
"level": "write",
"location": "Rng",
"device_name": "Alpha I",