Skip to content

Commit

Permalink
feature(project) Send an account verification link via email
Browse files Browse the repository at this point in the history
  • Loading branch information
Mireille Niwemuhuza authored and Elie Mugenzi committed Jun 14, 2019
1 parent 0941dce commit f2df6dd
Show file tree
Hide file tree
Showing 22 changed files with 633 additions and 123 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ LOCAL_DB_USER=
LOCAL_DB_PASSWORD=
LOCAL_DB_NAME=
TEST_DATABASE_URL=
AUTHOSHAVEN_USER=
AUTHOSHAVEN_PASS=
BASE_URL=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ coverage
.node_repl_history

.env

2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

language: node_js
node_js:
- "stable"
Expand All @@ -13,3 +14,4 @@ before_install:
- export PATH="$HOME/.yarn/bin:$PATH"
after_success:
- yarn run coverage

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
"serve": "node dist/index.js",
"start": "node dist/index.js",
"test": "nyc --reporter=html --reporter=text mocha ./test/*.js --exit --require @babel/register --require regenerator-runtime",
"coverage": "nyc report --reporter=text-lcov | coveralls"
"coverage": "nyc report --reporter=text-lcov | coveralls",
"test:local": "yarn undo && yarn migrate && yarn seed && yarn test",
"migrate": "sequelize db:migrate",
"undo": "sequelize db:migrate:undo",
"seed": "sequelize db:seed:all"
},
"author": "Andela Simulations Programme",
"license": "MIT",
"dependencies": {
"@hapi/joi": "^15.0.3",
"bcrypt": "^3.0.6",
"body-parser": "^1.18.3",
"cors": "^2.8.4",
"dotenv": "^8.0.0",
Expand All @@ -29,6 +34,7 @@
"method-override": "^2.3.10",
"methods": "^1.1.2",
"morgan": "^1.9.1",
"nodemailer": "^6.2.1",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"pg": "^7.11.0",
Expand Down
91 changes: 88 additions & 3 deletions src/api/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,89 @@
import db from '../../sequelize/models';
import TokenHelper from '../../helpers/Token.helper';
import Mailhelper from '../../helpers/SendMail.helper';
import HashHelper from '../../helpers/hashHelper';

export default {
signup: () => {},
};
const { User } = db;

/**
* @author Elie Mugenzi
* @class AuthController
* @description this class performs the whole authentication
*/
class AuthController {
/**
*
* @param {Object} req - Request object
* @param {Object} res - Response object
* @returns {Object} - Response object
*/
static async register(req, res) {
const {
firstName, lastName, email, password, username, dob, bio, gender
} = req.body;
if (Object.keys(req.body).length === 0) {
return res.status(400).json({
status: 400,
error: 'No data sent'
});
}
const newUser = await User.create({
firstName,
lastName,
email,
password: HashHelper.hashPassword(password),
username,
dob,
bio,
gender,
verified: false
});
if (newUser) {
const token = await TokenHelper.generateToken({ user: newUser });
Mailhelper.sendMail({
to: newUser.email,
names: `${newUser.firstName} ${newUser.lastName}`,
subject: 'Welcome to Authorshaven',
message: 'Thank you for choosing Authorshaven',
token
});

res.status(201).json({
status: 201,
message: 'We have sent an email to you to verify your account',
token,
});
}
}

/**
* Verifies account
* @param {Object} req - Request
* @param {*} res - Response
* @returns {Object} - Response
*/
static async verifyAccount(req, res) {
const { token } = req.query;
try {
const user = await TokenHelper.decodeToken(token);
await User.update({
verified: true
}, {
where: {
email: user.user.email
}
});
res.status(202).json({
status: 202,
message: 'Account is now verified!'
});
} catch (err) {
res.status(400).json({
status: 400,
error: 'Invalid Request'
});
}
}
}

export default AuthController;
6 changes: 4 additions & 2 deletions src/api/routes/authRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { Router } from 'express';
import authController from '../controllers/auth';
import validateBody from '../../middleware/validateBody';
import userValidation from '../../middleware/validUser';
import validateGender from '../../middleware/validateGender';

const authRouter = Router();
const { signup } = authController;
const { usernameExists, emailExists } = userValidation;
const { register, verifyAccount } = authController;

authRouter.post('/signup', validateBody('signup'), usernameExists, emailExists, signup);
authRouter.post('/signup', validateBody('signup'), validateGender, usernameExists, emailExists, register);
authRouter.get('/verify', verifyAccount);

export default authRouter;
44 changes: 44 additions & 0 deletions src/helpers/MailTemplate.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import dotenv from 'dotenv';

dotenv.config();

const mailTemplate = ({
to,
token,
names
}) => {
const template = `
<div style="background:#e5eeff;width:100%;padding:20px 0;">
<div style="max-width:760px;margin:0 auto;background:#ffffff">
<div style="background:#266cef;padding:10px;color:#ffffff;text-align:center;font-size:34px">
Authors Haven - Team Tesla
</div>
<div style="padding:0;">
</div>
<div style="padding:20px;text-align:left;">
<p>
Well ${names}, congratulations for choosing AuthorsHaven.
To verify that ${to} is your email, could you please click this link below to verify your AuthorsHaven's account?
<br/>
<a href="${process.env.BASE_URL}/api/auth/verify/?token=${token}">Click here to verify your account</a>
<br/>
Here there is the link below where you can visit Andela and get more information about what's Andela
</p>
<a href="https://andela.com">Visit Andela's website</a>
</div>
<br>
<div style="padding:20px;text-align:left;">
<b>Andela, Team @Tesla - Cohort 5</b>
</div>
</div>
<div style="padding:35px 10px;text-align:center;">
Copyright, 2019<br>
Andela, Team Tesla
</div>
</div>
`;
return template;
};

export default mailTemplate;
38 changes: 38 additions & 0 deletions src/helpers/SendMail.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import mailer from 'nodemailer';
import mailTemplate from './MailTemplate.helper';

const transporter = mailer.createTransport({
service: 'gmail',
auth: {
user: process.env.AUTHOSHAVEN_USER,
pass: process.env.AUTHOSHAVEN_PASS
}
});
/**
* @author Elie Mugenzi
* @class MailHelper
* @description A helper class for sending emails
*/
class MailHelper {
/**
* Send mail
* @param {Object} param0 - Object which contains email information
* @returns {Object} Results after sending mail
*/
static async sendMail({
to, names, subject, message, token
}) {
const msg = {
from: `Authors Haven<${process.env.AUTHOSHAVEN_USER}>`,
to,
subject,
text: message,
html: mailTemplate({ to, token, names })
};
const result = await transporter.sendMail(msg);
return result;
}
}

export default MailHelper;
2 changes: 0 additions & 2 deletions src/helpers/Token.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ class Tokenizer {
return user;
}
}


export default Tokenizer;
2 changes: 1 addition & 1 deletion src/helpers/dbUrlParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import url from 'url';

dotenv.config();

export default async (dbUrl) => {
export default (dbUrl) => {
const urlObj = url.parse(dbUrl);
const {
auth,
Expand Down
27 changes: 27 additions & 0 deletions src/helpers/hashHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import bcrypt from 'bcrypt';

/**
* @class HashHelper
*/
class HashHelper {
/**
* Hashes password
* @param {String} password - Password to hash
* @returns {String} - hashed Password
*/
static hashPassword(password) {
return bcrypt.hashSync(password, 8);
}

/**
* Compares Passwords
* @param {String} password - Password provided by a user
* @param {String} passwordToCompare - Password from Database
* @returns {Boolean} -True if they're equal, otherwise false
*/
static comparePassword(password, passwordToCompare) {
return bcrypt.compareSync(password, passwordToCompare);
}
}

export default HashHelper;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import db from './sequelize/models/index';

const { sequelize } = db;


dotenv.config();
const port = process.env.PORT || 3000;
const app = express();
Expand Down
6 changes: 2 additions & 4 deletions src/middleware/validUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ const validUser = {
message: 'This email is already in use',
});
}
next();
});

next();
},
async usernameExists(req, res, next) {
const { username } = req.body;
Expand All @@ -31,9 +30,8 @@ const validUser = {
message: 'This username is not available, Please choose another one!',
});
}
next();
});

next();
},
};

Expand Down
17 changes: 17 additions & 0 deletions src/middleware/validateGender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const validategender = (req, res, next) => {
const { gender } = req.body;
if (gender) {
if (gender === 'M' || gender === 'F') {
next();
} else {
res.status(400).json({
status: 400,
error: 'Gender should be represented by either "M" or "F" '
});
}
} else {
next();
}
};

export default validategender;
57 changes: 57 additions & 0 deletions src/sequelize/migrations/20190612172050-create-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
},
username: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
},
bio: {
type: Sequelize.TEXT
},
image: {
type: Sequelize.TEXT
},
dateOfBirth: {
type: Sequelize.DATE
},
gender: {
type: Sequelize.STRING
},
provider: {
type: Sequelize.STRING
},
socialId: {
type: Sequelize.STRING
},
verified: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('Users')
};
Loading

0 comments on commit f2df6dd

Please sign in to comment.