From ddb779ebd98fe527a7a541e0d00911ce9ae358ae Mon Sep 17 00:00:00 2001 From: Martin Grotz Date: Tue, 20 Oct 2020 21:25:00 +0200 Subject: [PATCH] feat: add registration flow with email verification --- README.md | 9 +- _helpers/send-email.js | 15 ++++ _middleware/error-handler.js | 19 ++++ _middleware/validate-request.js | 19 ++++ accounts/account.service.js | 149 ++++++++++++++++++++++++++++++++ accounts/accounts.controller.js | 88 +++++++++++++++++++ app.js | 50 ++++++----- config/email.js | 15 ++++ config/passport.js | 12 ++- models/User.js | 48 +++++----- package-lock.json | 48 ++++++++++ package.json | 4 +- project-logo.png | Bin 53176 -> 0 bytes routes/api/index.js | 7 +- routes/api/users.js | 60 +++++-------- 15 files changed, 454 insertions(+), 89 deletions(-) create mode 100644 _helpers/send-email.js create mode 100644 _middleware/error-handler.js create mode 100644 _middleware/validate-request.js create mode 100644 accounts/account.service.js create mode 100644 accounts/accounts.controller.js create mode 100644 config/email.js delete mode 100644 project-logo.png diff --git a/README.md b/README.md index a567681..327ddb4 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,11 @@ Uploading a track to the local server requires multiple steps, as uploading is n - Import the [Postman](https://www.postman.com) script "add-track.json" from the "postman-examples" into Postman - In each of the three requests add your user id in the "Pre-request script" tab as the value for the "UserId" variable - As tracks have to be split into smaller parts to get a working upload from the sensor you have to run the three requests in the order of: begin -> add -> end -- View your freshly uploaded track at (http://localhost:4200) -> Home -> Your feed \ No newline at end of file +- View your freshly uploaded track at (http://localhost:4200) -> Home -> Your feed + +### Sending E-Mails +By default in development mode mails are not sent, but instead the mail data is logged to the console. This can be overriden with the `--devSendMails` flag if you start the application like so: `npm run dev -- --devSendMails`. + +Mails are also always sent in production mode! + +For actually sending e-mails the user and password for the SMTP server need to be specified as environment variables. The username is read from `MAILUSER`, and the password is read from `MAILPW`, so in local development startup would like something like this (at least in Linux): `MAILUSER=myuser MAILPW=supersecurepassword npm run dev -- --devSendMails`. \ No newline at end of file diff --git a/_helpers/send-email.js b/_helpers/send-email.js new file mode 100644 index 0000000..b2167b1 --- /dev/null +++ b/_helpers/send-email.js @@ -0,0 +1,15 @@ +const nodemailer = require('nodemailer'); +const config = require('../config/email'); + +module.exports = sendEmail; + +async function sendEmail({ to, subject, html, from = config.emailFrom }) { + if (config.sendMails) { + const transporter = nodemailer.createTransport(config.smtpOptions); + await transporter.sendMail({ from, to, subject, html }); + } else { + console.log({ + to, subject, html, from + }); + } +} \ No newline at end of file diff --git a/_middleware/error-handler.js b/_middleware/error-handler.js new file mode 100644 index 0000000..c7d4cee --- /dev/null +++ b/_middleware/error-handler.js @@ -0,0 +1,19 @@ +module.exports = errorHandler; + +function errorHandler(err, req, res, next) { + switch (true) { + case typeof err === 'string': + // custom application error + const is404 = err.toLowerCase().endsWith('not found'); + const statusCode = is404 ? 404 : 400; + return res.status(statusCode).json({ message: err }); + case err.name === 'ValidationError': + // mongoose validation error + return res.status(400).json({ message: err.message }); + case err.name === 'UnauthorizedError': + // jwt authentication error + return res.status(401).json({ message: 'Unauthorized' }); + default: + return res.status(500).json({ message: err.message }); + } +} \ No newline at end of file diff --git a/_middleware/validate-request.js b/_middleware/validate-request.js new file mode 100644 index 0000000..caa45f4 --- /dev/null +++ b/_middleware/validate-request.js @@ -0,0 +1,19 @@ +module.exports = validateRequest; + +function validateRequest(req, next, schema) { + console.log('validateRequest'); + + const options = { + abortEarly: false, // include all errors + allowUnknown: true, // ignore unknown props + stripUnknown: true // remove unknown props + }; + const { error, value } = schema.validate(req.body, options); + if (error) { + console.log('error: ', error) + next(`Validation error: ${error.details.map(x => x.message).join(', ')}`); + } else { + req.body = value; + next(); + } +} \ No newline at end of file diff --git a/accounts/account.service.js b/accounts/account.service.js new file mode 100644 index 0000000..70936b2 --- /dev/null +++ b/accounts/account.service.js @@ -0,0 +1,149 @@ +const crypto = require("crypto"); +const mongoose = require('mongoose'); +const sendEmail = require('../_helpers/send-email'); +var User = mongoose.model('User'); + +module.exports = { + register, + verifyEmail, + forgotPassword, + validateResetToken, + resetPassword, +}; + +async function register(params, origin) { + const user = await User.findOne({ email: params.email }); + + if (user) { + // send already registered error in email to prevent account enumeration + return await sendAlreadyRegisteredEmail(params.email, origin); + } + + const newUser = new User(); + + newUser.username = params.username; + newUser.email = params.email; + newUser.setPassword(params.password); + newUser.verificationToken = randomTokenString(); + newUser.needsEmailValidation = true; + + await newUser.save(); + + // send email + await sendVerificationEmail(newUser, origin); +} + +async function verifyEmail({ token }) { + const account = await User.findOne({ verificationToken: token }); + + if (!account) throw 'Verification failed'; + + account.needsEmailValidation = false; + account.verificationToken = undefined; + await account.save(); +} + +async function forgotPassword({ email }, origin) { + const account = await User.findOne({ email }); + + console.log('forgotPassword', account, email); + + // always return ok response to prevent email enumeration + if (!account) return; + + // create reset token that expires after 24 hours + account.resetToken = { + token: randomTokenString(), + expires: new Date(Date.now() + 24 * 60 * 60 * 1000) + }; + await account.save(); + + console.log('forgotPassword account saved', account); + + + // send email + await sendPasswordResetEmail(account, origin); +} + +async function validateResetToken({ token }) { + const account = await User.findOne({ + 'resetToken.token': token, + 'resetToken.expires': { $gt: Date.now() } + }); + + if (!account) throw 'Invalid token'; +} + +async function resetPassword({ token, password }) { + const account = await User.findOne({ + 'resetToken.token': token, + 'resetToken.expires': { $gt: Date.now() } + }); + + if (!account) throw 'Invalid token'; + + // update password and remove reset token + account.setPassword(password) + account.resetToken = undefined; + await account.save(); +} + +function randomTokenString() { + return crypto.randomBytes(40).toString('hex'); +} + +async function sendVerificationEmail(account, origin) { + let message; + if (origin) { + const verifyUrl = `${origin}/account/verify-email?token=${account.verificationToken}`; + message = `

Please click the below link to verify your email address:

+

${verifyUrl}

`; + } else { + message = `

Please use the below token to verify your email address with the /account/verify-email api route:

+

${account.verificationToken}

`; + } + + await sendEmail({ + to: account.email, + subject: 'Sign-up Verification API - Verify Email', + html: `

Verify Email

+

Thanks for registering!

+ ${message}` + }); +} + +async function sendAlreadyRegisteredEmail(email, origin) { + let message; + if (origin) { + message = `

If you don't know your password please visit the forgot password page.

`; + } else { + message = `

If you don't know your password you can reset it via the /account/forgot-password api route.

`; + } + + await sendEmail({ + to: email, + subject: 'Sign-up Verification API - Email Already Registered', + html: `

Email Already Registered

+

Your email ${email} is already registered.

+ ${message}` + }); +} + +async function sendPasswordResetEmail(account, origin) { + let message; + if (origin) { + const resetUrl = `${origin}/account/reset-password?token=${account.resetToken.token}`; + message = `

Please click the below link to reset your password, the link will be valid for 1 day:

+

${resetUrl}

`; + } else { + message = `

Please use the below token to reset your password with the /account/reset-password api route:

+

${account.resetToken.token}

`; + } + + await sendEmail({ + to: account.email, + subject: 'Sign-up Verification API - Reset Password', + html: `

Reset Password Email

+ ${message}` + }); +} \ No newline at end of file diff --git a/accounts/accounts.controller.js b/accounts/accounts.controller.js new file mode 100644 index 0000000..680585b --- /dev/null +++ b/accounts/accounts.controller.js @@ -0,0 +1,88 @@ +const express = require('express'); +const router = express.Router(); +const Joi = require('joi'); +const validateRequest = require('../_middleware/validate-request'); +const accountService = require('./account.service'); + +// routes +router.post('/register', registerSchema, register); +router.post('/verify-email', verifyEmailSchema, verifyEmail); +router.post('/forgot-password', forgotPasswordSchema, forgotPassword); +router.post('/validate-reset-token', validateResetTokenSchema, validateResetToken); +router.post('/reset-password', resetPasswordSchema, resetPassword); + +module.exports = router; + +function registerSchema(req, res, next) { + const schema = Joi.object({ + username: Joi.string().required(), + email: Joi.string().email().required(), + password: Joi.string().min(6).required(), + confirmPassword: Joi.string().valid(Joi.ref('password')).required() + }); + + validateRequest(req, next, schema); +} + +function register(req, res, next) { + accountService.register(req.body, req.get('origin')) + .then(() => res.json({ message: 'Registration successful, please check your email for verification instructions' })) + .catch((err) => { + console.log(err); + next(err) + }); +} + +function verifyEmailSchema(req, res, next) { + const schema = Joi.object({ + token: Joi.string().required() + }); + validateRequest(req, next, schema); +} + +function verifyEmail(req, res, next) { + accountService.verifyEmail(req.body) + .then(() => res.json({ message: 'Verification successful, you can now login' })) + .catch(next); +} + +function forgotPasswordSchema(req, res, next) { + const schema = Joi.object({ + email: Joi.string().email().required() + }); + validateRequest(req, next, schema); +} + +function forgotPassword(req, res, next) { + accountService.forgotPassword(req.body, req.get('origin')) + .then(() => res.json({ message: 'Please check your email for password reset instructions' })) + .catch(next); +} + +function validateResetTokenSchema(req, res, next) { + const schema = Joi.object({ + token: Joi.string().required() + }); + validateRequest(req, next, schema); +} + +function validateResetToken(req, res, next) { + accountService.validateResetToken(req.body) + .then(() => res.json({ message: 'Token is valid' })) + .catch(next); +} + +function resetPasswordSchema(req, res, next) { + const schema = Joi.object({ + token: Joi.string().required(), + password: Joi.string().min(6).required(), + confirmPassword: Joi.string().valid(Joi.ref('password')).required() + }); + validateRequest(req, next, schema); +} + +function resetPassword(req, res, next) { + accountService.resetPassword(req.body) + .then(() => res.json({ message: 'Password reset successful, you can now login' })) + .catch(next); +} diff --git a/app.js b/app.js index 1e730f5..91ffacb 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,13 @@ var http = require('http'), - path = require('path'), - methods = require('methods'), - express = require('express'), - bodyParser = require('body-parser'), - session = require('express-session'), - cors = require('cors'), - passport = require('passport'), - errorhandler = require('errorhandler'), - mongoose = require('mongoose'); + path = require('path'), + methods = require('methods'), + express = require('express'), + bodyParser = require('body-parser'), + session = require('express-session'), + cors = require('cors'), + passport = require('passport'), + errorhandler = require('errorhandler'), + mongoose = require('mongoose'); var isProduction = process.env.NODE_ENV === 'production'; @@ -24,13 +24,13 @@ app.use(bodyParser.json()); app.use(require('method-override')()); app.use(express.static(__dirname + '/public')); -app.use(session({ secret: 'conduit', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); +app.use(session({ secret: 'obsobs', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false })); if (!isProduction) { app.use(errorhandler()); } -if(isProduction){ +if (isProduction) { mongoose.connect('mongodb://localhost/obs'); } else { mongoose.connect('mongodb://localhost/obsTest'); @@ -46,7 +46,7 @@ require('./config/passport'); app.use(require('./routes')); /// catch 404 and forward to error handler -app.use(function(req, res, next) { +app.use(function (req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); @@ -57,29 +57,33 @@ app.use(function(req, res, next) { // development error handler // will print stacktrace if (!isProduction) { - app.use(function(err, req, res, next) { + app.use(function (err, req, res, next) { console.log(err.stack); res.status(err.status || 500); - res.json({'errors': { - message: err.message, - error: err - }}); + res.json({ + 'errors': { + message: err.message, + error: err + } + }); }); } // production error handler // no stacktraces leaked to user -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { res.status(err.status || 500); - res.json({'errors': { - message: err.message, - error: {} - }}); + res.json({ + 'errors': { + message: err.message, + error: {} + } + }); }); // finally, let's start our server... -var server = app.listen( process.env.PORT || 3000, function(){ +var server = app.listen(process.env.PORT || 3000, function () { console.log('Listening on port ' + server.address().port); }); diff --git a/config/email.js b/config/email.js new file mode 100644 index 0000000..38884d6 --- /dev/null +++ b/config/email.js @@ -0,0 +1,15 @@ +const isProduction = process.env.NODE_ENV === 'production'; +const forcedMail = process.argv.findIndex(s => s === '--devSendMails') !== -1; + +module.exports = { + "sendMails": isProduction || forcedMail, + "emailFrom": "noreply@openbikesensor.org", + "smtpOptions": { + "host": "mail.your-server.de", + "port": 587, + "auth": { + "user": process.env.MAILUSER, + "pass": process.env.MAILPW + } + } +}; \ No newline at end of file diff --git a/config/passport.js b/config/passport.js index abe0ce2..4150cc9 100644 --- a/config/passport.js +++ b/config/passport.js @@ -6,10 +6,14 @@ var User = mongoose.model('User'); passport.use(new LocalStrategy({ usernameField: 'user[email]', passwordField: 'user[password]' -}, function(email, password, done) { - User.findOne({email: email}).then(function(user){ - if(!user || !user.validPassword(password)){ - return done(null, false, {errors: {'email or password': 'is invalid'}}); +}, function (email, password, done) { + User.findOne({ email: email }).then(function (user) { + if (!user || !user.validPassword(password)) { + return done(null, false, { errors: { 'email or password': 'is invalid' } }); + } + + if (user && user.needsEmailValidation) { + return done(null, false, { errors: { 'E-Mail-Bestätigung': 'noch nicht erfolgt' } }); } return done(null, user); diff --git a/models/User.js b/models/User.js index 1b94331..9d7fb6f 100644 --- a/models/User.js +++ b/models/User.js @@ -5,30 +5,36 @@ var jwt = require('jsonwebtoken'); var secret = require('../config').secret; var UserSchema = new mongoose.Schema({ - username: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9]+$/, 'is invalid'], index: true}, - email: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true}, + username: { type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9]+$/, 'is invalid'], index: true }, + email: { type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true }, bio: String, image: String, favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Article' }], following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], areTracksVisibleForAll: Boolean, hash: String, - salt: String -}, {timestamps: true}); + salt: String, + needsEmailValidation: Boolean, + verificationToken: String, + resetToken: { + token: String, + expires: Date + } +}, { timestamps: true }); -UserSchema.plugin(uniqueValidator, {message: 'is already taken.'}); +UserSchema.plugin(uniqueValidator, { message: 'ist bereits vergeben. Sorry!' }); -UserSchema.methods.validPassword = function(password) { +UserSchema.methods.validPassword = function (password) { var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); return this.hash === hash; }; -UserSchema.methods.setPassword = function(password){ +UserSchema.methods.setPassword = function (password) { this.salt = crypto.randomBytes(16).toString('hex'); this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); }; -UserSchema.methods.generateJWT = function() { +UserSchema.methods.generateJWT = function () { var today = new Date(); var exp = new Date(today); exp.setDate(today.getDate() + 60); @@ -40,7 +46,7 @@ UserSchema.methods.generateJWT = function() { }, secret); }; -UserSchema.methods.toAuthJSON = function(){ +UserSchema.methods.toAuthJSON = function () { return { username: this.username, email: this.email, @@ -52,7 +58,7 @@ UserSchema.methods.toAuthJSON = function(){ }; }; -UserSchema.methods.toProfileJSONFor = function(user){ +UserSchema.methods.toProfileJSONFor = function (user) { return { username: this.username, bio: this.bio, @@ -61,45 +67,45 @@ UserSchema.methods.toProfileJSONFor = function(user){ }; }; -UserSchema.methods.favorite = function(id){ - if(this.favorites.indexOf(id) === -1){ +UserSchema.methods.favorite = function (id) { + if (this.favorites.indexOf(id) === -1) { this.favorites.push(id); } return this.save(); }; -UserSchema.methods.unfavorite = function(id){ +UserSchema.methods.unfavorite = function (id) { this.favorites.remove(id); return this.save(); }; -UserSchema.methods.isFavorite = function(id){ - return this.favorites.some(function(favoriteId){ +UserSchema.methods.isFavorite = function (id) { + return this.favorites.some(function (favoriteId) { return favoriteId.toString() === id.toString(); }); }; -UserSchema.methods.isTrackVisible = function(id){ +UserSchema.methods.isTrackVisible = function (id) { return this.areTracksVisibleForAll(); }; -UserSchema.methods.follow = function(id){ - if(this.following.indexOf(id) === -1){ +UserSchema.methods.follow = function (id) { + if (this.following.indexOf(id) === -1) { this.following.push(id); } return this.save(); }; -UserSchema.methods.unfollow = function(id){ +UserSchema.methods.unfollow = function (id) { this.following.remove(id); return this.save(); }; -UserSchema.methods.isFollowing = function(id){ - return this.following.some(function(followId){ +UserSchema.methods.isFollowing = function (id) { + return this.following.some(function (followId) { return followId.toString() === id.toString(); }); }; diff --git a/package-lock.json b/package-lock.json index 60e48e8..1067256 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,37 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@hapi/address": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", + "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + }, + "@hapi/hoek": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.0.tgz", + "integrity": "sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw==" + }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@postman/form-data": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.0.tgz", @@ -1416,6 +1447,18 @@ } } }, + "joi": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.2.1.tgz", + "integrity": "sha512-YT3/4Ln+5YRpacdmfEfrrKh50/kkgX3LgBltjqnlMPIYiZ4hxXZuVJcxmsvxsdeHg9soZfE3qXxHC2tMpCCBOA==", + "requires": { + "@hapi/address": "^4.1.0", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, "js-sha512": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", @@ -1916,6 +1959,11 @@ "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", "dev": true }, + "nodemailer": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.14.tgz", + "integrity": "sha512-0AQHOOT+nRAOK6QnksNaK7+5vjviVvEBzmZytKU7XSA+Vze2NLykTx/05ti1uJgXFTWrMq08u3j3x4r4OE6PAA==" + }, "nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", diff --git a/package.json b/package.json index 7f59547..946599b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "express": "4.17.1", "express-jwt": "^6.0.0", "express-session": "1.17.1", + "joi": "^17.2.1", "jsonwebtoken": "8.5.1", "latest": "^0.2.0", "method-override": "3.0.0", @@ -29,6 +30,7 @@ "mongoose": "^5.10.7", "mongoose-unique-validator": "2.0.3", "morgan": "1.10.0", + "nodemailer": "^6.4.14", "passport": "0.4.1", "passport-local": "1.0.0", "request": "2.88.2", @@ -39,4 +41,4 @@ "newman": "^5.2.0", "nodemon": "^2.0.4" } -} \ No newline at end of file +} diff --git a/project-logo.png b/project-logo.png deleted file mode 100644 index ef92a4be8c6cb93a3e9c78d72787e558a40cd149..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53176 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

-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 T4&^Tm3_#%N>gTe~DWM4fEO&TD diff --git a/routes/api/index.js b/routes/api/index.js index 6970fab..e33ca1f 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -4,11 +4,12 @@ router.use('/', require('./users')); router.use('/profiles', require('./profiles')); router.use('/tracks', require('./tracks')); router.use('/tags', require('./tags')); +router.use('/accounts', require('../../accounts/accounts.controller')); -router.use(function(err, req, res, next){ - if(err.name === 'ValidationError'){ +router.use(function (err, req, res, next) { + if (err.name === 'ValidationError') { return res.status(422).json({ - errors: Object.keys(err.errors).reduce(function(errors, key){ + errors: Object.keys(err.errors).reduce(function (errors, key) { errors[key] = err.errors[key].message; return errors; diff --git a/routes/api/users.js b/routes/api/users.js index bbaa913..a2f254f 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -4,75 +4,63 @@ var passport = require('passport'); var User = mongoose.model('User'); var auth = require('../auth'); -router.get('/user', auth.required, function(req, res, next){ - User.findById(req.payload.id).then(function(user){ - if(!user){ return res.sendStatus(401); } +router.get('/user', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { return res.sendStatus(401); } - return res.json({user: user.toAuthJSON()}); + return res.json({ user: user.toAuthJSON() }); }).catch(next); }); -router.put('/user', auth.required, function(req, res, next){ - User.findById(req.payload.id).then(function(user){ - if(!user){ return res.sendStatus(401); } +router.put('/user', auth.required, function (req, res, next) { + User.findById(req.payload.id).then(function (user) { + if (!user) { return res.sendStatus(401); } // only update fields that were actually passed... - if(typeof req.body.user.username !== 'undefined'){ + if (typeof req.body.user.username !== 'undefined') { user.username = req.body.user.username; } - if(typeof req.body.user.email !== 'undefined'){ + if (typeof req.body.user.email !== 'undefined') { user.email = req.body.user.email; } - if(typeof req.body.user.bio !== 'undefined'){ + if (typeof req.body.user.bio !== 'undefined') { user.bio = req.body.user.bio; } - if(typeof req.body.user.image !== 'undefined'){ + if (typeof req.body.user.image !== 'undefined') { user.image = req.body.user.image; } - if(typeof req.body.user.areTracksVisibleForAll !== 'undefined'){ + if (typeof req.body.user.areTracksVisibleForAll !== 'undefined') { user.areTracksVisibleForAll = req.body.user.areTracksVisibleForAll; } - if(typeof req.body.user.password !== 'undefined' && req.body.user.password !='' ){ + if (typeof req.body.user.password !== 'undefined' && req.body.user.password != '') { user.setPassword(req.body.user.password); } - return user.save().then(function(){ - return res.json({user: user.toAuthJSON()}); + return user.save().then(function () { + return res.json({ user: user.toAuthJSON() }); }); }).catch(next); }); -router.post('/users/login', function(req, res, next){ - if(!req.body.user.email){ - return res.status(422).json({errors: {email: "can't be blank"}}); +router.post('/users/login', function (req, res, next) { + if (!req.body.user.email) { + return res.status(422).json({ errors: { email: "can't be blank" } }); } - if(!req.body.user.password){ - return res.status(422).json({errors: {password: "can't be blank"}}); + if (!req.body.user.password) { + return res.status(422).json({ errors: { password: "can't be blank" } }); } - passport.authenticate('local', {session: false}, function(err, user, info){ - if(err){ return next(err); } + passport.authenticate('local', { session: false }, function (err, user, info) { + if (err) { return next(err); } - if(user){ + if (user) { user.token = user.generateJWT(); - return res.json({user: user.toAuthJSON()}); + return res.json({ user: user.toAuthJSON() }); } else { return res.status(422).json(info); } })(req, res, next); }); -router.post('/users', function(req, res, next){ - var user = new User(); - - user.username = req.body.user.username; - user.email = req.body.user.email; - user.setPassword(req.body.user.password); - - user.save().then(function(){ - return res.json({user: user.toAuthJSON()}); - }).catch(next); -}); - module.exports = router;