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 ef92a4b..0000000
Binary files a/project-logo.png and /dev/null differ
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;