From 4e68267bfd361dd5c39d30bd87d56f94c50828c5 Mon Sep 17 00:00:00 2001 From: VLE2FE Date: Thu, 23 Apr 2020 17:46:00 +0200 Subject: [PATCH] added passreset and mail helper --- oas/schemas.yaml | 11 +++-- oas/user.yaml | 8 ++-- package-lock.json | 26 ++++++++++++ package.json | 1 + src/db.ts | 1 + src/helpers/mail.ts | 64 ++++++++++++++++++++++++++++ src/routes/user.spec.ts | 92 +++++++++++++++++++++++++++++++++++++++++ src/routes/user.ts | 25 ++++++++++- 8 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 src/helpers/mail.ts diff --git a/oas/schemas.yaml b/oas/schemas.yaml index 82c9508..d4151d1 100644 --- a/oas/schemas.yaml +++ b/oas/schemas.yaml @@ -141,14 +141,17 @@ Email: email: type: string example: john.doe@bosch.com -User: - allOf: - - $ref: 'oas.yaml#/components/schemas/_Id' - - $ref: 'oas.yaml#/components/schemas/Email' +UserName: properties: name: type: string example: johndoe +User: + 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 diff --git a/oas/user.yaml b/oas/user.yaml index 3db2b3c..4042901 100644 --- a/oas/user.yaml +++ b/oas/user.yaml @@ -171,11 +171,13 @@ content: application/json: schema: - $ref: 'oas.yaml#/components/schemas/Email' + allOf: + - $ref: 'oas.yaml#/components/schemas/UserName' + - $ref: 'oas.yaml#/components/schemas/Email' responses: 200: $ref: 'oas.yaml#/components/responses/Ok' - 401: - $ref: 'oas.yaml#/components/responses/401' + 404: + $ref: 'oas.yaml#/components/responses/404' 500: $ref: 'oas.yaml#/components/responses/500' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 795376c..b8354bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -203,6 +203,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -848,6 +856,24 @@ "is-buffer": "~2.0.3" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "form-data": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", diff --git a/package.json b/package.json index 7630a0c..d5a2bfe 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@hapi/joi": "^17.1.1", "@types/mocha": "^5.2.7", "@types/node": "^13.1.6", + "axios": "^0.19.2", "basic-auth": "^2.0.1", "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", diff --git a/src/db.ts b/src/db.ts index de45a74..b93fb6f 100644 --- a/src/db.ts +++ b/src/db.ts @@ -51,6 +51,7 @@ export default class db { }); }); mongoose.connection.once('open', () => { + mongoose.set('useFindAndModify', false); console.log(process.env.NODE_ENV === 'test' ? '' : `Connected to ${connectionString}`); this.state.db = mongoose.connection; done(); diff --git a/src/helpers/mail.ts b/src/helpers/mail.ts new file mode 100644 index 0000000..949d243 --- /dev/null +++ b/src/helpers/mail.ts @@ -0,0 +1,64 @@ +import axios from 'axios'; + +// sends an email + +export default (mailAddress, subject, content, f) => { // callback, executed empty or with error + if (process.env.NODE_ENV === 'production') { + const mailService = JSON.parse(process.env.VCAP_SERVICES).Mail[0]; + axios({ + method: 'post', + url: mailService.credentials.uri + '/email', + auth: {username: mailService.credentials.username, password: mailService.credentials.password}, + data: { + recipients: [{to: mailAddress}], + subject: {content: subject}, + body: { + content: content, + contentType: "text/html" + }, + from: { + eMail: "dfop@bosch-iot.com", + password: "PlasticsOfFingerprintDigital" + } + } + }) + .then(() => { + f(); + }) + .catch((err) => { + f(err); + }); + } + else if (process.env.NODE_ENV === 'test') { + console.log('Sending mail to ' + mailAddress + ': -- ' + subject + ' -- ' + content); + f(); + } + else { // dev + axios({ + method: 'get', + url: 'https://digital-fingerprint-of-plastics-mail-test.apps.de1.bosch-iot-cloud.com/api', + data: { + method: 'post', + url: '/email', + data: { + recipients: [{to: mailAddress}], + subject: {content: subject}, + body: { + content: content, + contentType: "text/html" + }, + from: { + eMail: "dfop-test@bosch-iot.com", + password: "PlasticsOfFingerprintDigital" + } + } + } + }) + .then(() => { + f(); + }) + .catch((err) => { + f(err); + }); + } +} \ No newline at end of file diff --git a/src/routes/user.spec.ts b/src/routes/user.spec.ts index f78d387..a6ebec0 100644 --- a/src/routes/user.spec.ts +++ b/src/routes/user.spec.ts @@ -105,4 +105,96 @@ describe('/user/new', () => { done(); }); }); +}); + + + + +describe('/user/passreset', () => { + 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 the ok response', done => { + supertest(server) + .post('/user/passreset') + .send({ + email: 'jane.doe@bosch.com', + name: 'janedoe' + }) + .expect('Content-type', /json/) + .expect(200) + .end((err, res) => { + if (err) done(err); + should(res.body).be.eql({status: 'OK'}); + done(); + }); + }); + it('returns 404 for wrong username/email combo', done => { + supertest(server) + .post('/user/passreset') + .send({ + email: 'jane.doe@bosch.com', + name: 'admin' + }) + .expect('Content-type', /json/) + .expect(404) + .end((err, res) => { + if (err) done(err); + should(res.body).be.eql({status: 'Not found'}); + done(); + }); + }); + it('returns 404 for unknown username', done => { + supertest(server) + .post('/user/passreset') + .send({ + email: 'jane.doe@bosch.com', + name: 'admin' + }) + .expect('Content-type', /json/) + .expect(404) + .end((err, res) => { + if (err) done(err); + should(res.body).be.eql({status: 'Not found'}); + done(); + }); + }); + it('changes the user password', done => { + UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + if (err) return done(err); + const oldpass = data[0].pass; + supertest(server) + .post('/user/passreset') + .send({ + email: 'jane.doe@bosch.com', + name: 'janedoe' + }) + .expect('Content-type', /json/) + .expect(200) + .end((err, res) => { + if (err) done(err); + should(res.body).be.eql({status: 'OK'}); + UserModel.find({name: 'janedoe'}).lean().exec( 'find', (err, data) => { + if (err) return done(err); + should(data[0].pass).not.eql(oldpass); + done(); + }); + }); + }); + }); }); \ No newline at end of file diff --git a/src/routes/user.ts b/src/routes/user.ts index ffaebc7..e4a17d5 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -3,6 +3,7 @@ import mongoose from 'mongoose'; import bcrypt from 'bcryptjs'; import UserValidate from './validate/user'; import UserModel from '../models/user'; +import mail from '../helpers/mail'; const router = express.Router(); @@ -11,7 +12,6 @@ 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 @@ -40,4 +40,27 @@ router.post('/user/new', (req, res, next) => { }); }); +router.post('/user/passreset', (req, res, next) => { + // check if user/email combo exists + UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( 'find', (err, data) => { + if (err) next(err); + if (data.length === 1) { // it exists + const newPass = Math.random().toString(36).substring(2); + bcrypt.hash(newPass, 10, (err, hash) => { // password hashing + if (err) next(err); + UserModel.findOneAndUpdate({name: req.body.name, email: req.body.email}, {pass: hash}, err => { + if (err) next(err); + mail(data[0].email, 'Your new password for the DFOP database', 'Hi,

You requested to reset your password.
Your new password is:

' + newPass + '

If you did not request a password reset, talk to the sysadmin quickly!

Have a nice day.

The DFOP team', err => { + if (err) next(err); + res.json({status: 'OK'}); + }); + }); + }); + } + else { + res.status(404).json({status: 'Not found'}); + } + }); +}); + module.exports = router; \ No newline at end of file