feat: add registration flow with email verification
This commit is contained in:
parent
3fb02b5809
commit
ddb779ebd9
|
@ -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
|
- 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
|
- 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
|
- 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
|
- 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`.
|
15
_helpers/send-email.js
Normal file
15
_helpers/send-email.js
Normal file
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
_middleware/error-handler.js
Normal file
19
_middleware/error-handler.js
Normal file
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
19
_middleware/validate-request.js
Normal file
19
_middleware/validate-request.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
149
accounts/account.service.js
Normal file
149
accounts/account.service.js
Normal file
|
@ -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 = `<p>Please click the below link to verify your email address:</p>
|
||||||
|
<p><a href="${verifyUrl}">${verifyUrl}</a></p>`;
|
||||||
|
} else {
|
||||||
|
message = `<p>Please use the below token to verify your email address with the <code>/account/verify-email</code> api route:</p>
|
||||||
|
<p><code>${account.verificationToken}</code></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendEmail({
|
||||||
|
to: account.email,
|
||||||
|
subject: 'Sign-up Verification API - Verify Email',
|
||||||
|
html: `<h4>Verify Email</h4>
|
||||||
|
<p>Thanks for registering!</p>
|
||||||
|
${message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendAlreadyRegisteredEmail(email, origin) {
|
||||||
|
let message;
|
||||||
|
if (origin) {
|
||||||
|
message = `<p>If you don't know your password please visit the <a href="${origin}/account/forgot-password">forgot password</a> page.</p>`;
|
||||||
|
} else {
|
||||||
|
message = `<p>If you don't know your password you can reset it via the <code>/account/forgot-password</code> api route.</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendEmail({
|
||||||
|
to: email,
|
||||||
|
subject: 'Sign-up Verification API - Email Already Registered',
|
||||||
|
html: `<h4>Email Already Registered</h4>
|
||||||
|
<p>Your email <strong>${email}</strong> is already registered.</p>
|
||||||
|
${message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendPasswordResetEmail(account, origin) {
|
||||||
|
let message;
|
||||||
|
if (origin) {
|
||||||
|
const resetUrl = `${origin}/account/reset-password?token=${account.resetToken.token}`;
|
||||||
|
message = `<p>Please click the below link to reset your password, the link will be valid for 1 day:</p>
|
||||||
|
<p><a href="${resetUrl}">${resetUrl}</a></p>`;
|
||||||
|
} else {
|
||||||
|
message = `<p>Please use the below token to reset your password with the <code>/account/reset-password</code> api route:</p>
|
||||||
|
<p><code>${account.resetToken.token}</code></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendEmail({
|
||||||
|
to: account.email,
|
||||||
|
subject: 'Sign-up Verification API - Reset Password',
|
||||||
|
html: `<h4>Reset Password Email</h4>
|
||||||
|
${message}`
|
||||||
|
});
|
||||||
|
}
|
88
accounts/accounts.controller.js
Normal file
88
accounts/accounts.controller.js
Normal file
|
@ -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);
|
||||||
|
}
|
50
app.js
50
app.js
|
@ -1,13 +1,13 @@
|
||||||
var http = require('http'),
|
var http = require('http'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
methods = require('methods'),
|
methods = require('methods'),
|
||||||
express = require('express'),
|
express = require('express'),
|
||||||
bodyParser = require('body-parser'),
|
bodyParser = require('body-parser'),
|
||||||
session = require('express-session'),
|
session = require('express-session'),
|
||||||
cors = require('cors'),
|
cors = require('cors'),
|
||||||
passport = require('passport'),
|
passport = require('passport'),
|
||||||
errorhandler = require('errorhandler'),
|
errorhandler = require('errorhandler'),
|
||||||
mongoose = require('mongoose');
|
mongoose = require('mongoose');
|
||||||
|
|
||||||
var isProduction = process.env.NODE_ENV === 'production';
|
var isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
|
@ -24,13 +24,13 @@ app.use(bodyParser.json());
|
||||||
app.use(require('method-override')());
|
app.use(require('method-override')());
|
||||||
app.use(express.static(__dirname + '/public'));
|
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) {
|
if (!isProduction) {
|
||||||
app.use(errorhandler());
|
app.use(errorhandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isProduction){
|
if (isProduction) {
|
||||||
mongoose.connect('mongodb://localhost/obs');
|
mongoose.connect('mongodb://localhost/obs');
|
||||||
} else {
|
} else {
|
||||||
mongoose.connect('mongodb://localhost/obsTest');
|
mongoose.connect('mongodb://localhost/obsTest');
|
||||||
|
@ -46,7 +46,7 @@ require('./config/passport');
|
||||||
app.use(require('./routes'));
|
app.use(require('./routes'));
|
||||||
|
|
||||||
/// catch 404 and forward to error handler
|
/// 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');
|
var err = new Error('Not Found');
|
||||||
err.status = 404;
|
err.status = 404;
|
||||||
next(err);
|
next(err);
|
||||||
|
@ -57,29 +57,33 @@ app.use(function(req, res, next) {
|
||||||
// development error handler
|
// development error handler
|
||||||
// will print stacktrace
|
// will print stacktrace
|
||||||
if (!isProduction) {
|
if (!isProduction) {
|
||||||
app.use(function(err, req, res, next) {
|
app.use(function (err, req, res, next) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
|
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500);
|
||||||
|
|
||||||
res.json({'errors': {
|
res.json({
|
||||||
message: err.message,
|
'errors': {
|
||||||
error: err
|
message: err.message,
|
||||||
}});
|
error: err
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// production error handler
|
// production error handler
|
||||||
// no stacktraces leaked to user
|
// 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.status(err.status || 500);
|
||||||
res.json({'errors': {
|
res.json({
|
||||||
message: err.message,
|
'errors': {
|
||||||
error: {}
|
message: err.message,
|
||||||
}});
|
error: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// finally, let's start our server...
|
// 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);
|
console.log('Listening on port ' + server.address().port);
|
||||||
});
|
});
|
||||||
|
|
15
config/email.js
Normal file
15
config/email.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -6,10 +6,14 @@ var User = mongoose.model('User');
|
||||||
passport.use(new LocalStrategy({
|
passport.use(new LocalStrategy({
|
||||||
usernameField: 'user[email]',
|
usernameField: 'user[email]',
|
||||||
passwordField: 'user[password]'
|
passwordField: 'user[password]'
|
||||||
}, function(email, password, done) {
|
}, function (email, password, done) {
|
||||||
User.findOne({email: email}).then(function(user){
|
User.findOne({ email: email }).then(function (user) {
|
||||||
if(!user || !user.validPassword(password)){
|
if (!user || !user.validPassword(password)) {
|
||||||
return done(null, false, {errors: {'email or password': 'is invalid'}});
|
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);
|
return done(null, user);
|
||||||
|
|
|
@ -5,30 +5,36 @@ var jwt = require('jsonwebtoken');
|
||||||
var secret = require('../config').secret;
|
var secret = require('../config').secret;
|
||||||
|
|
||||||
var UserSchema = new mongoose.Schema({
|
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},
|
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},
|
email: { type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true },
|
||||||
bio: String,
|
bio: String,
|
||||||
image: String,
|
image: String,
|
||||||
favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Article' }],
|
favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Article' }],
|
||||||
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
|
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
|
||||||
areTracksVisibleForAll: Boolean,
|
areTracksVisibleForAll: Boolean,
|
||||||
hash: String,
|
hash: String,
|
||||||
salt: String
|
salt: String,
|
||||||
}, {timestamps: true});
|
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');
|
var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
|
||||||
return this.hash === hash;
|
return this.hash === hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.setPassword = function(password){
|
UserSchema.methods.setPassword = function (password) {
|
||||||
this.salt = crypto.randomBytes(16).toString('hex');
|
this.salt = crypto.randomBytes(16).toString('hex');
|
||||||
this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').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 today = new Date();
|
||||||
var exp = new Date(today);
|
var exp = new Date(today);
|
||||||
exp.setDate(today.getDate() + 60);
|
exp.setDate(today.getDate() + 60);
|
||||||
|
@ -40,7 +46,7 @@ UserSchema.methods.generateJWT = function() {
|
||||||
}, secret);
|
}, secret);
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.toAuthJSON = function(){
|
UserSchema.methods.toAuthJSON = function () {
|
||||||
return {
|
return {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
@ -52,7 +58,7 @@ UserSchema.methods.toAuthJSON = function(){
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.toProfileJSONFor = function(user){
|
UserSchema.methods.toProfileJSONFor = function (user) {
|
||||||
return {
|
return {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
bio: this.bio,
|
bio: this.bio,
|
||||||
|
@ -61,45 +67,45 @@ UserSchema.methods.toProfileJSONFor = function(user){
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.favorite = function(id){
|
UserSchema.methods.favorite = function (id) {
|
||||||
if(this.favorites.indexOf(id) === -1){
|
if (this.favorites.indexOf(id) === -1) {
|
||||||
this.favorites.push(id);
|
this.favorites.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.unfavorite = function(id){
|
UserSchema.methods.unfavorite = function (id) {
|
||||||
this.favorites.remove(id);
|
this.favorites.remove(id);
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.isFavorite = function(id){
|
UserSchema.methods.isFavorite = function (id) {
|
||||||
return this.favorites.some(function(favoriteId){
|
return this.favorites.some(function (favoriteId) {
|
||||||
return favoriteId.toString() === id.toString();
|
return favoriteId.toString() === id.toString();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.isTrackVisible = function(id){
|
UserSchema.methods.isTrackVisible = function (id) {
|
||||||
return this.areTracksVisibleForAll();
|
return this.areTracksVisibleForAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
UserSchema.methods.follow = function(id){
|
UserSchema.methods.follow = function (id) {
|
||||||
if(this.following.indexOf(id) === -1){
|
if (this.following.indexOf(id) === -1) {
|
||||||
this.following.push(id);
|
this.following.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.unfollow = function(id){
|
UserSchema.methods.unfollow = function (id) {
|
||||||
this.following.remove(id);
|
this.following.remove(id);
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
UserSchema.methods.isFollowing = function(id){
|
UserSchema.methods.isFollowing = function (id) {
|
||||||
return this.following.some(function(followId){
|
return this.following.some(function (followId) {
|
||||||
return followId.toString() === id.toString();
|
return followId.toString() === id.toString();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
48
package-lock.json
generated
48
package-lock.json
generated
|
@ -4,6 +4,37 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"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": {
|
"@postman/form-data": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.0.tgz",
|
"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": {
|
"js-sha512": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz",
|
||||||
|
@ -1916,6 +1959,11 @@
|
||||||
"integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==",
|
"integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==",
|
||||||
"dev": true
|
"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": {
|
"nodemon": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"express-jwt": "^6.0.0",
|
"express-jwt": "^6.0.0",
|
||||||
"express-session": "1.17.1",
|
"express-session": "1.17.1",
|
||||||
|
"joi": "^17.2.1",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"latest": "^0.2.0",
|
"latest": "^0.2.0",
|
||||||
"method-override": "3.0.0",
|
"method-override": "3.0.0",
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
"mongoose": "^5.10.7",
|
"mongoose": "^5.10.7",
|
||||||
"mongoose-unique-validator": "2.0.3",
|
"mongoose-unique-validator": "2.0.3",
|
||||||
"morgan": "1.10.0",
|
"morgan": "1.10.0",
|
||||||
|
"nodemailer": "^6.4.14",
|
||||||
"passport": "0.4.1",
|
"passport": "0.4.1",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"request": "2.88.2",
|
"request": "2.88.2",
|
||||||
|
@ -39,4 +41,4 @@
|
||||||
"newman": "^5.2.0",
|
"newman": "^5.2.0",
|
||||||
"nodemon": "^2.0.4"
|
"nodemon": "^2.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
project-logo.png
BIN
project-logo.png
Binary file not shown.
Before Width: | Height: | Size: 52 KiB |
|
@ -4,11 +4,12 @@ router.use('/', require('./users'));
|
||||||
router.use('/profiles', require('./profiles'));
|
router.use('/profiles', require('./profiles'));
|
||||||
router.use('/tracks', require('./tracks'));
|
router.use('/tracks', require('./tracks'));
|
||||||
router.use('/tags', require('./tags'));
|
router.use('/tags', require('./tags'));
|
||||||
|
router.use('/accounts', require('../../accounts/accounts.controller'));
|
||||||
|
|
||||||
router.use(function(err, req, res, next){
|
router.use(function (err, req, res, next) {
|
||||||
if(err.name === 'ValidationError'){
|
if (err.name === 'ValidationError') {
|
||||||
return res.status(422).json({
|
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;
|
errors[key] = err.errors[key].message;
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|
|
@ -4,75 +4,63 @@ var passport = require('passport');
|
||||||
var User = mongoose.model('User');
|
var User = mongoose.model('User');
|
||||||
var auth = require('../auth');
|
var auth = require('../auth');
|
||||||
|
|
||||||
router.get('/user', auth.required, function(req, res, next){
|
router.get('/user', auth.required, function (req, res, next) {
|
||||||
User.findById(req.payload.id).then(function(user){
|
User.findById(req.payload.id).then(function (user) {
|
||||||
if(!user){ return res.sendStatus(401); }
|
if (!user) { return res.sendStatus(401); }
|
||||||
|
|
||||||
return res.json({user: user.toAuthJSON()});
|
return res.json({ user: user.toAuthJSON() });
|
||||||
}).catch(next);
|
}).catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put('/user', auth.required, function(req, res, next){
|
router.put('/user', auth.required, function (req, res, next) {
|
||||||
User.findById(req.payload.id).then(function(user){
|
User.findById(req.payload.id).then(function (user) {
|
||||||
if(!user){ return res.sendStatus(401); }
|
if (!user) { return res.sendStatus(401); }
|
||||||
|
|
||||||
// only update fields that were actually passed...
|
// 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;
|
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;
|
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;
|
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;
|
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;
|
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);
|
user.setPassword(req.body.user.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.save().then(function(){
|
return user.save().then(function () {
|
||||||
return res.json({user: user.toAuthJSON()});
|
return res.json({ user: user.toAuthJSON() });
|
||||||
});
|
});
|
||||||
}).catch(next);
|
}).catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/users/login', function(req, res, next){
|
router.post('/users/login', function (req, res, next) {
|
||||||
if(!req.body.user.email){
|
if (!req.body.user.email) {
|
||||||
return res.status(422).json({errors: {email: "can't be blank"}});
|
return res.status(422).json({ errors: { email: "can't be blank" } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!req.body.user.password){
|
if (!req.body.user.password) {
|
||||||
return res.status(422).json({errors: {password: "can't be blank"}});
|
return res.status(422).json({ errors: { password: "can't be blank" } });
|
||||||
}
|
}
|
||||||
|
|
||||||
passport.authenticate('local', {session: false}, function(err, user, info){
|
passport.authenticate('local', { session: false }, function (err, user, info) {
|
||||||
if(err){ return next(err); }
|
if (err) { return next(err); }
|
||||||
|
|
||||||
if(user){
|
if (user) {
|
||||||
user.token = user.generateJWT();
|
user.token = user.generateJWT();
|
||||||
return res.json({user: user.toAuthJSON()});
|
return res.json({ user: user.toAuthJSON() });
|
||||||
} else {
|
} else {
|
||||||
return res.status(422).json(info);
|
return res.status(422).json(info);
|
||||||
}
|
}
|
||||||
})(req, res, next);
|
})(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;
|
module.exports = router;
|
||||||
|
|
Loading…
Reference in a new issue