Skip to content

Commit

Permalink
feat(verification): add mail verification after signup
Browse files Browse the repository at this point in the history
- test the verification email method
- add send verification method as helper
- add verification method as controller

[Finishes #166789869]
  • Loading branch information
Karangwa Hirwa Julien authored and Karangwa Hirwa Julien committed Jul 8, 2019
1 parent 50e619a commit c7e941b
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 198 deletions.
1 change: 1 addition & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ HEROKU_DATABASE_USERNAME="heroku-database-username"
HEROKU_SECRET_KEY="heroku-database-password"
HEROKU_HOST="heroku-host"
HEROKU_DATABASE="heroku-database-name"
SendGridKey=""
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,38 @@
"errorhandler": "^1.5.0",
"express": "^4.17.1",
"express-session": "^1.15.6",
"googleapis": "^40.0.1",
"jsonwebtoken": "^8.5.1",
"method-override": "^2.3.10",
"methods": "^1.1.2",
"morgan": "^1.9.1",
"nodemailer": "^6.2.1",
"nyc": "^14.1.1",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"regenerator-runtime": "^0.13.2",
"request": "^2.87.0",
"sendgrid": "^5.2.3",
"sequelize": "^5.8.12",
"sequelize-cli": "^5.5.0",
"swagger-jsdoc": "^1.3.0",
"swagger-ui-express": "^4.0.2",
"underscore": "^1.9.1"
},
"devDependencies": {
"mocha": "^6.1.4",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-polyfill": "^6.26.0",
"babel-register": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"coveralls": "^3.0.4",
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"mocha": "^6.1.4",
"mocha-lcov-reporter": "^1.3.0",
"nodemon": "^1.19.1"
}
Expand Down
71 changes: 57 additions & 14 deletions src/controllers/userControllers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import bcrypt from 'bcrypt';
import dotenv from 'dotenv';
import model from '../db/models/index';
import mail from '../helpers/mail';
import validations from '../helpers/validations';
import processToken from '../helpers/processToken';

import sendVerificationEmail from '../helpers/sendVerificationEmail';

dotenv.config();

const { Users } = model;

/**
Expand All @@ -19,25 +24,18 @@ class UserManager {
static async registerUser(req, res) {
try {
const {
username, email, password, bio, image
username, email, password, bio
} = req.body;
const user = {
username, email, hash: password, bio, image: null
username, email, hash: password, isVerified: false, bio, image: null
};

const payload = { username, email };
const token = await processToken.signToken(payload);

await Users.create(user);
sendVerificationEmail.send(token, email);
return res.status(201).json({
message: 'user registered succesfully',
user: {
email,
token,
username,
bio,
image
}
message: 'Thank you for registration, You should check your email for verification',
});
} catch (error) {
return res.status(409).json({
Expand All @@ -48,6 +46,42 @@ class UserManager {

/**
*
* @param {Object} req
* @param {Object} res
* @returns {Object} verification message
*/
static async verification(req, res) {
try {
const findUser = await Users.findOne({
where: { email: req.query.email }
});

if (findUser) {
if (findUser.isVerified) {
return res.status(202).json({
message: 'Email already Verified.'
});
}
await Users.update({ isVerified: true }, { where: { id: findUser.id } });
return res.status(403).json({
message: `User with ${findUser.email} has been verified, use this link to login: https://ah-lobos-backend-swagger.herokuapp.com`,
user: {
email: findUser.email,
token: req.query.token,
username: findUser.username,
bio: findUser.bio,
image: null
}
});
}
} catch (error) {
return res.status(500).json({
message: 'internal server error! please try again later'
});
}
}

/**
* @param {object} req
* @param {object} res
* @returns {Object} user object
Expand All @@ -59,8 +93,17 @@ class UserManager {
});

if (findUser) {
const { username, email, hash } = findUser.dataValues;
const userData = { username, email, hash };
const {
username, email, hash, isVerified
} = findUser.dataValues;
const userData = {
username, email, hash, isVerified
};
if (!findUser.dataValues.isVerified) {
return res.status(401).json({
message: 'Please check your email and click the button for email verification'
});
}

if (bcrypt.compareSync(req.body.password, userData.hash)) {
const payload = {
Expand All @@ -69,7 +112,7 @@ class UserManager {
};
const token = await processToken.signToken(payload);
return res.status(200).json({
message: 'login succesfull',
message: 'User has been successfully logged in',
user: {
token,
email: payload.email,
Expand Down
6 changes: 6 additions & 0 deletions src/db/config/envirnoment.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ envConfig.db_password_pro = process.env.HEROKU_SECRET_KEY;
envConfig.db_host_pro = process.env.HEROKU_HOST;
envConfig.db_database_pro = process.env.HEROKU_DATABASE;

envConfig.send_grid_key = process.env.SendGridApiKey;
envConfig.host = process.env.HOST;
envConfig.token = process.env.SECRET_JWT_KEY;
envConfig.email = process.env.EMAIL;
envConfig.password = process.env.PASSWORD;

export default envConfig;
3 changes: 3 additions & 0 deletions src/db/migrations/20190625140719-create-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ module.exports = {
following: {
type: Sequelize.STRING
},
isVerified: {
type: Sequelize.BOOLEAN
},
hash: {
type: Sequelize.STRING
},
Expand Down
2 changes: 0 additions & 2 deletions src/db/models/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


import { readdirSync } from 'fs';
import { basename as _basename, join } from 'path';
import Sequelize from 'sequelize';
Expand Down
3 changes: 3 additions & 0 deletions src/db/models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export default (sequelize, DataTypes) => {
args: true
}
}],
isVerified: {
type: DataTypes.BOOLEAN
},
hash: DataTypes.STRING
}, {
hooks: {
Expand Down
62 changes: 62 additions & 0 deletions src/helpers/sendVerificationEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import nodemailer from 'nodemailer';
import { google } from 'googleapis';
import config from '../db/config/envirnoment';

const { OAuth2 } = google.auth;

/**
* Send Email Verification
*/
class SendVerificationEmail {
// eslint-disable-next-line valid-jsdoc
/**
*
* @param {string} token
* @param {string} email
* @return message sent
*/
static async send(token, email) {
const oauth2Client = new OAuth2(
'207391721395-nr9q4f02giavmn6bj91lgosf6ordht9h.apps.googleusercontent.com',
'nfjp3BycPRO-kmExVAMSZjqu', // Client Secret
'https://developers.google.com/oauthplayground' // Redirect URL
);

await oauth2Client.setCredentials({
refresh_token: '1/8NY6gHcgF7MzygPf2Fd-YkkVsRRW53xYC5-UpWuACXuyB8I82ozKwpDhL7_mOwxo'
});
const tokens = await oauth2Client.refreshAccessToken();
const accessToken = tokens.credentials.access_token;

const smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: 'juliushirwa@gmail.com',
clientId: '207391721395-nr9q4f02giavmn6bj91lgosf6ordht9h.apps.googleusercontent.com',
clientSecret: 'nfjp3BycPRO-kmExVAMSZjqu',
refreshToken: '1/8NY6gHcgF7MzygPf2Fd-YkkVsRRW53xYC5-UpWuACXuyB8I82ozKwpDhL7_mOwxo',
accessToken
}
});

const mailOptions = {
from: 'juliushirwa@gmail.com',
to: email,
subject: `Hello ${email}`,
text: 'Lobos is giving you an opportunity to write and read your stories.',
generateTextFromHTML: true,
html: `<p>Hello There, <br />
Thank you for registering to Authors Haven Lobos,<br />
we are happy to continue to serve you. click the following button to verify your email.<form action='${config.host}/api/verification?token=${token}&email=${email}' method='post'><input type='submit' value='Verify email' style='margin-left: 80px; padding-top: 8px; padding-bottom: 8px; padding-left: 20px; padding-right: 20px; background-color: green; color: white; border-radius: 5px; cursor: pointer;'/></form></p>
<br />
<p>Thanks, regards Lobos</p>
`
};

return smtpTransport.sendMail(mailOptions);
}
}


export default SendVerificationEmail;
1 change: 1 addition & 0 deletions src/helpers/validations.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-unresolved
import joi from '@hapi/joi';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ app.use(routes);
// Access swagger ui documentation on this route
app.use('/', swaggerUI.serve, swaggerUI.setup(swaggerJSDoc));

app.use('/*', (req, res) => res.status(200).send({
app.get('*', (req, res) => res.status(200).send({
message: 'Welcome to the default Authors Haven API route',
}));

Expand Down
1 change: 1 addition & 0 deletions src/routes/api/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import express from 'express';

import users from './users';

const router = express.Router();
Expand Down
1 change: 1 addition & 0 deletions src/routes/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import userController from '../../controllers/userControllers';
const router = express.Router();

router.post('/users', userController.registerUser);
router.post('/verification', userController.verification);
router.post('/users/login', userController.login);
router.post('/user/forgot-password', userController.forgotPassword);
router.post('/user/reset-password/:userToken', userController.resetPassword);
Expand Down
Loading

0 comments on commit c7e941b

Please sign in to comment.