Skip to content

Commit

Permalink
Merge fa300be into 9c336f9
Browse files Browse the repository at this point in the history
  • Loading branch information
conquext committed Aug 23, 2019
2 parents 9c336f9 + fa300be commit 784b488
Show file tree
Hide file tree
Showing 24 changed files with 512 additions and 56 deletions.
22 changes: 15 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
DB_USERNAME=postgres
DB_PASSWORD=db_password
DB_NAME=barefoot_nomad
DB_HOST=127.0.0.1
PORT=

DB_TEST_NAME=barefoot_test
DB_USERNAME=postgres
DB_PASSWORD=db_password
DB_NAME=barefoot_nomad
DB_HOST=127.0.0.1
PORT=""
DB_TEST_NAME=barefoot_test
TOKENEXPIRY=18000
EMAIL=
CLIENT_URL="https://firestar_backend.herokuapp.com"
HOST="localhost"
DATABASE_URL_TEST=""
NODE_ENV="test"
YOUR_EMAIL=""
SOME_PASSWORD=""
SENDGRIP_API=""
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"author": "Andela Simulations Programme",
"license": "MIT",
"dependencies": {
"@sendgrid/mail": "^6.4.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
"core-js": "^3.2.1",
"cors": "^2.8.4",
Expand All @@ -39,13 +41,16 @@
"make-runnable": "^1.3.6",
"method-override": "^2.3.10",
"methods": "^1.1.2",
"moment": "^2.24.0",
"morgan": "^1.9.1",
"nodemailer": "^6.3.0",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"pg": "^7.12.1",
"pg-hstore": "^2.3.3",
"regenerator-runtime": "^0.13.3",
"request": "^2.87.0",
"sequelize": "^5.15.0",
"sequelize": "^5.15.2",
"sequelize-cli": "^5.5.0",
"swagger-ui-express": "^4.0.7",
"underscore": "^1.9.1"
Expand All @@ -68,4 +73,4 @@
"nodemon": "^1.19.1",
"nyc": "^14.1.1"
}
}
}
131 changes: 131 additions & 0 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Router } from 'express';
import moment from 'moment';
import crypto from 'crypto';
import Validation from '../validation';
import { sendResetMail, sendSignupMail } from '../services/sendMail';
import { errorResponse, successResponse } from '../utils/response';
import Hash from '../utils/hash';
import models from '../models';

const User = models.User;
const Login = models.Login;
const Reset = models.Reset;

const forgotPassword = (req, res) => {
const { errors, isValid } = Validation.validateEmail(req.body);

// Check validation
if (!isValid) {
return errorResponse(res, 400, errors);
}

const { email } = req.body;

// Find user by email
User.findOne({ where: { email } }).then(user => {
// Check for user
if (!user) {
return sendSignupMail(user);
}

const newReset = new Reset({
id: user.id,
email: req.body.email,
reset_token: '',
created_on: new Date(),
expire_time: moment
.utc()
.add(process.env.TOKENEXPIRY, 'seconds')
.toLocaleString()
});

// Generate Reset token
const resetToken = crypto.randomBytes(32).toString('hex');
Hash.hash(resetToken).then(resetHash => {
newReset.reset_token = resetHash;
// Remove all reset token for this user if it exists
Reset.findOne({
where: {
email: newReset.dataValues.email
}
}).then(result => {
return Reset.destroy({
where: { email: newReset.dataValues.email }
})
.then(() => {
newReset
.save()
// Send reset link to user email
.then(() => {
sendResetMail(user.dataValues, resetToken);
});
})
.then(() =>
successResponse(res, 200, 'Check your mail for further instruction')
)
.catch(error => errorResponse(res, 500, error));
});
});
});
};

/**
* @description Resets a user password
* @static
* @param {*} req
* @param {*} res
* @returns Promise {UserController} A new password record
* @memberof UserController
*/
const resetPassword = (req, res) => {
const { errors, isValid } = Validation.validatePassword(req.body);

// Check validation
if (!isValid) {
return errorResponse(res, 400, errors);
}

const { id } = req.params;
const resetToken = req.query.token;
const { password } = req.body;

// Find user by email
Reset.findOne({ where: { id } }).then(user => {
// Check if user has requested password reset
if (user) {
// Check if reset token is not expired
const expireTime = moment.utc(user.expire_time);

// If reset link is valid and not expired
if (
moment().isBefore(expireTime) &&
Hash.compareWithHash(resetToken, user.reset_token)
) {
// Store hash of new password in login
Hash.hash(password)
.then(hashed => {
Login.update(
{
token: '',
password: hashed,
logged_in: false,
last_login: new Date()
},
{ where: { email: user.email } }
);
})
// Delete reset request from database
.then(() => Reset.destroy({ where: { email: user.email } }))
.catch(error => errorResponse(res, 500, error));
return successResponse(res, 200, 'Password Updated successfully');
}
return errorResponse(res, 400, 'Invalid or expired reset token');
}
return errorResponse(res, 400, 'Invalid or expired reset token');
});
};

export default {
forgotPassword,
resetPassword
};
31 changes: 31 additions & 0 deletions src/database/seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import models from '../models';
import dotenv from 'dotenv';

const seedCopyDb = async date => {
await models.User.create({
email: 'youremail@andela.com',
role: 'passenger'
});

await models.User.create({
email: process.env.YOUR_EMAIL,
role: 'driver'
});

await models.Login.create({
email: 'youremail@andela.com',
password: 'password'
});

await models.Login.create({
email: process.env.YOUR_EMAIL,
password: process.env.SOME_PASSWORD
});

await models.Reset.create({
email: 'youremail@andela.com',
password: 'password'
});
};

export default seedCopyDb;
26 changes: 17 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import swaggerUi from 'swagger-ui-express';
import Log from 'debug';
import routes from './routes';
import swaggerDocument from '../swagger.json';
import { sequelize } from './models';
import seedCopyDb from './database/seed';

const serverLog = Log('server');

Expand All @@ -35,8 +37,8 @@ app.use(
secret: 'authorshaven',
cookie: { maxAge: 60000 },
resave: false,
saveUninitialized: false,
}),
saveUninitialized: false
})
);

if (!isProduction) {
Expand Down Expand Up @@ -66,8 +68,8 @@ if (!isProduction) {
res.json({
errors: {
message: err.message,
error: err,
},
error: err
}
});
});
}
Expand All @@ -80,12 +82,18 @@ app.use((err, req, res, next) => {
res.json({
errors: {
message: err.message,
error: {},
},
error: {}
}
});
});

// finally, let's start our server...
const server = app.listen(process.env.PORT || 3000, () => {
serverLog(`Listening on port ${server.address().port}`);
// finally, let's connect to the database and start our server...
sequelize.sync({ force: !isProduction }).then(async () => {
if (!isProduction) {
seedCopyDb(new Date());
}

const server = app.listen(process.env.PORT || 3000, () => {
serverLog(`Listening on port ${server.address().port}`);
});
});
44 changes: 44 additions & 0 deletions src/models/User.js
100755 → 100644
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';
const users = (sequelize, DataTypes) => {
const User = sequelize.define('user', {
id: {
type: DataTypes.INTEGER,
unique: true,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
validate: {
notEmpty: true,
isEmail: true
}
},
phoneNumber: {
type: DataTypes.BIGINT,
unique: true
},
role: {
type: DataTypes.STRING
}
});

User.associate = models => {
User.hasOne(models.Login, {
foreignKey: 'id'
});

User.hasOne(models.Reset, {
foreignKey: 'id'
});
};

return User;
};

export default users;
42 changes: 23 additions & 19 deletions src/models/index.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
import fs from 'fs';
import path from 'path';
import Sequelize from 'sequelize';

const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.js')[env];
const db = {};

let sequelize;
console.log('config', config);
console.log('env', env);
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
}

fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
// Import the models
const models = {
User: sequelize.import('./user.js'),
Login: sequelize.import('./login.js'),
Reset: sequelize.import('./reset.js')
};

// and combine those models and resolve their associations using the Sequelize API
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
if (models[modelName].associate) {
models[modelName].associate(db);
}
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;
export { sequelize };

module.exports = db;
export default models;
Loading

0 comments on commit 784b488

Please sign in to comment.