Skip to content

Commit

Permalink
Bug(email verification): fix email verification
Browse files Browse the repository at this point in the history
- ensure user get valid link for verifying his/her email account
- ensure user login after verified his/her email

[Delivers #165525643]
  • Loading branch information
blaisebakundukize committed Apr 26, 2019
1 parent 9744556 commit 0ebef45
Show file tree
Hide file tree
Showing 15 changed files with 105 additions and 85 deletions.
2 changes: 2 additions & 0 deletions .env_sample
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ SENDER_EMAIL =
SENDER_PASS =

secretOrKey=

LOCALHOST_PORT=
27 changes: 11 additions & 16 deletions controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,44 +37,39 @@ class UserController {
};
const token = jwt.sign(payload, secretKey, expirationTime);
const response = await sendEmail(user.email, token);
return res.status(201).json({
const registeredUser = {
email: user.email,
token,
username: user.username,
bio: user.bio,
image: user.image,
emailResponse: response
});
} catch (error) {
return res.status(500).json({ error: error.message });
}
};
if (process.env.NODE_ENV === 'test') registeredUser.token = token;
return res.status(201).json({ registeredUser });
} catch (error) { return res.status(500).json({ error: error.message }); }
}

/**
* user login
* @param {Object} req -requestesting from user
* @param {Object} req -requesting from user
* @param {Object} res -responding from user
* @returns {Object} Response with status of 201
*/
login(req, res) {
const user = {
email: req.body.email,
password: req.body.password
};
const user = { email: req.body.email, password: req.body.password };
return User.findOne({ where: { email: user.email } })
.then((foundUser) => {
if (foundUser && bcrypt.compareSync(user.password, foundUser.password)) {
if (foundUser.isActivated === false) {
return res.status(403).send({ status: 403, error: 'Verify your email account before login.' });
}
const payload = {
id: foundUser.id,
email: foundUser.email,
isAdmin: foundUser.isAdmin
};
const token = jwt.sign(payload, secretKey, expirationTime);
res.status(200).json({
status: 200,
token,
user: payload
});
res.status(200).json({ status: 200, token, user: payload });
} else {
res.status(400).json({ status: 400, error: 'Incorrect username or password' });
}
Expand Down
9 changes: 7 additions & 2 deletions helpers/sendEmail/emailTemplates.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import dotenv from 'dotenv';

dotenv.config();
const url = (process.env.NODE_ENV === 'production') ? 'https://badass-ah-backend-staging.herokuapp.com' : `http://127.0.0.1:${process.env.LOCALHOST_PORT}`;

/**
* An object module that holds mails' templates
* @exports email/templates
Expand All @@ -9,7 +14,7 @@ const emailTemplates = {
subject: 'Email Verification',
html: `<h1 style="color: #444; margin-left: 20px;">Welcome to Author's Haven</h1>
<p style="color: #555; margin-left: 20px; font-size: 14px">Thank you for signing to the Author's Haven. Please click on the button below to activate your account.</p><br>
<a style="background-color: #61a46e; padding: 12px 15px 12px 15px; color: #eee; font-size: 16px; text-decoration: none; margin-left: 20px; cursor: pointer;" href="http://localhost:3000/api/users/verify/$">Activate account</a>`
<a style="background-color: #61a46e; padding: 12px 15px 12px 15px; color: #eee; font-size: 16px; text-decoration: none; margin-left: 20px; cursor: pointer;" href="${url}/api/users/verify/$token">Activate account</a>`
},

resetPassword: {
Expand All @@ -18,7 +23,7 @@ const emailTemplates = {
subject: 'Password Reset',
html: `<h1 style="color: #444; margin-left: 20px;">Password reset</h1>
<p style="color: #555; margin-left: 20px; font-size: 14px">Lost your password? click on the link below to reset your password.</p><br>
<a style="background-color: #61a46e; padding: 12px 15px 12px 15px; color: #eee; font-size: 16px; text-decoration: none; margin-left: 20px; cursor: pointer;" href="http://localhost:3000/api/users/password/$">Reset Password</a>`
<a style="background-color: #61a46e; padding: 12px 15px 12px 15px; color: #eee; font-size: 16px; text-decoration: none; margin-left: 20px; cursor: pointer;" href="${url}/api/users/password/$token">Reset Password</a>`
}
};

Expand Down
2 changes: 1 addition & 1 deletion helpers/sendEmail/mailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Mailer {
const mailOptions = emailTemplates[template];
mailOptions.from = this.senderEmail;
mailOptions.to = this.userEmail;
const addToken = mailOptions.html.replace('$', token);
const addToken = mailOptions.html.replace('$token', token);
mailOptions.html = addToken;
try {
const response = await this.sendEmail(mailOptions);
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import router from './routes/index';
import passportAuth from './middlewares/passport-facebook';

const app = express();
const port = process.env.PORT || 3000;
const port = process.env.PORT || process.env.LOCALHOST_PORT;

// @bodyParser configuration
app.use(express.urlencoded({ extended: false }));
Expand Down
6 changes: 3 additions & 3 deletions test/1-sendEmail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ describe('Email Verification Link', () => {
.send(testMailer)
.end((err, res) => {
if (err) done(err);
verifyLinkToken = res.body.token;
verifyLinkToken = res.body.registeredUser.token;
res.should.have.status(201);
res.body.should.have.property('token');
res.body.should.have.property('username');
res.body.registeredUser.should.have.property('token');
res.body.registeredUser.should.have.property('username');
done();
});
}).timeout(20000);
Expand Down
25 changes: 19 additions & 6 deletions test/2-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ describe('User ', () => {
.end((err, res) => {
if (err) done(err);
res.should.have.status(201);
res.body.should.have.property('token');
res.body.should.have.property('username');
res.body.registeredUser.should.have.property('token');
res.body.registeredUser.should.have.property('username');
done();
});
}).timeout(20000);
Expand All @@ -58,8 +58,8 @@ describe('User ', () => {
.end((err, res) => {
if (err) done(err);
res.should.have.status(201);
res.body.should.have.property('token');
res.body.should.have.property('username');
res.body.registeredUser.should.have.property('token');
res.body.registeredUser.should.have.property('username');
done();
});
}).timeout(50000);
Expand Down Expand Up @@ -97,13 +97,26 @@ describe('User ', () => {
});

// user login

it('should login user', (done) => {
it('should return error verify email account before login', (done) => {
chai
.request(app)
.post('/api/users/login')
.set('Content-Type', 'application/json')
.send(login1)
.end((err, res) => {
if (err) done(err);
res.should.have.status(403);
res.body.should.be.an('object');
done();
});
});
it('should login user', (done) => {
chai
.request(app)
.post('/api/users/login')
.set('Content-Type', 'application/json')
.send({ email: testMailer.email, password: testMailer.password })
.send(googleValidToken)
.end((err, res) => {
if (err) {
done(err);
Expand Down
22 changes: 16 additions & 6 deletions test/3-artilcle.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/* eslint-disable no-restricted-syntax */
import chai from 'chai';
import chaiHttp from 'chai-http';
import app from '../index';
import { article1 } from '../testingdata/article.json';
import shareArticle from '../helpers/shareArticles';
import { login1, login4 } from '../testingdata/user.json';
import { login4, testMailer } from '../testingdata/user.json';
import models from '../models/index';
import tag from '../helpers/tags';
import generateToken from '../helpers/token';

chai.use(chaiHttp);
chai.should();
const Article = models.article;
const { article: Article, user } = models;
let APItoken;
let APItoken2;
let articleId;
Expand Down Expand Up @@ -57,10 +57,19 @@ describe('Article', () => {
before(async () => {
try {
await Article.destroy({ where: { title: article1.title } });
const tokens = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json').send(login1);
const tokens = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json').send({
email: testMailer.email, password: testMailer.password
});
APItoken = `Bearer ${tokens.body.token}`;
const login2 = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json').send(login4);
APItoken2 = `Bearer ${login2.body.token}`;
// const login2 = await chai.request(app).
// .post('/api/users/login').set('Content-Type', 'application/json').send(login4);
const findLogin2 = await user.findOne({ where: { email: login4.email } });
const { generate } = generateToken({
id: findLogin2.id,
email: findLogin2.email
});
APItoken2 = `Bearer ${generate}`;
// APItoken2 = `Bearer ${login2.body.token}`;
} catch (error) {
console.log(error);
}
Expand Down Expand Up @@ -121,6 +130,7 @@ describe('Article', () => {
chai.request(app)
.get(`/api/articles/${articleId}`)
.set('Content-Type', 'application/json')
.set('Authorization', APItoken)
.end((error, res) => {
if (error) {
done(error);
Expand Down
4 changes: 2 additions & 2 deletions test/6-likes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
article1
} from '../testingdata/article.json';
import {
login1
testMailer
} from '../testingdata/user.json';

let APItoken;
Expand All @@ -19,7 +19,7 @@ describe('votes', () => {
const user = await chai.request(app)
.post('/api/users/login')
.set('Content-Type', 'application/json')
.send(login1);
.send({ email: testMailer.email, password: testMailer.password });
APItoken = `Bearer ${user.body.token}`;
const article = await chai.request(app)
.post('/api/articles')
Expand Down
17 changes: 9 additions & 8 deletions test/7-rate.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import chai from 'chai';
import chaiHttp from 'chai-http';
import app from '../index';
import { testMailer, signup1 } from '../testingdata/user.json';
import { testMailer, signup5 } from '../testingdata/user.json';
import { article1 } from '../testingdata/article.json';
import models from '../models/index';
import generateToken from '../helpers/token';

chai.use(chaiHttp);
chai.should();

const { rate, article } = models;
const { rate, article, user } = models;

describe('Article ratings and reading stats', () => {
let userToken;
Expand All @@ -23,13 +24,13 @@ describe('Article ratings and reading stats', () => {
userToken = `Bearer ${userLoggedIn.body.token}`;
await rate.destroy({ where: {}, truncate: true });
await article.destroy({ where: { title: article1.title } });
const unverifiedUserLoggedIn = await chai.request(app)
.post('/api/users/login')
.set('Content-Type', 'application/json')
.send({ email: signup1.email, password: signup1.password });
unverifiedUserToken = `Bearer ${unverifiedUserLoggedIn.body.token}`;

await rate.destroy({ where: {}, truncate: true });
const unverifiedUserLoggedIn = await user.findOne({ where: { email: signup5.email } });
const { generate } = generateToken({
id: unverifiedUserLoggedIn.id,
email: unverifiedUserLoggedIn.email
});
unverifiedUserToken = `Bearer ${generate}`;
} catch (error) {
throw new Error(error);
}
Expand Down
53 changes: 21 additions & 32 deletions test/8-comment.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
/* eslint-disable no-unused-vars */
import chai from 'chai';
import chaiHttp from 'chai-http';
import Sequelize from 'sequelize';
import app from '../index';
import models from '../models/index';
import { validateComment } from '../testingdata/comment.json';
import { login1, testMailer } from '../testingdata/user.json';
// import { login1, testMailer } from '../testingdata/user.json';
import { testMailer } from '../testingdata/user.json';

chai.use(chaiHttp);
chai.should();

const Comment = models.comments;
const wrondUserId = 848;
let validComment;
let userId;
let idArticle;
let idComment;

const invalidToken = 'EAAgbksnPT5IBAI2f478gPi5HZC9iAvldAtZCKhDPXaZCt0cTEr9kuDbETW1wZCDF17alOnG7qdKZB14O4rr2zg6gtkuU6Q14G9idx1JOZAHcFgtQam72PoBzvjgyyl1BxgiFGMHOwVGVPi23QilFQ1z2hUJCYCHyBYT6qfsfCmFwZDZD';
let token;
let token2;
// let token2;
describe('Comment', () => {
before(async () => {
try {
const loginUser = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json').send(login1);
const loginUser2 = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json').send(testMailer);
// const loginUser = await chai.request(app).post('/api/users/login')
// .set('Content-Type', 'application/json').send(login1);
// const loginUser2 = await chai.request(app).post('/api/users/login')
// .set('Content-Type', 'application/json').send(testMailer);
const loginUser = await chai.request(app).post('/api/users/login').set('Content-Type', 'application/json')
.send({ email: testMailer.email, password: testMailer.password });
token = `Bearer ${loginUser.body.token}`;
token2 = `Bearer ${loginUser2.body.token}`;
// token2 = `Bearer ${loginUser2.body.token}`;
userId = loginUser.body.id;

const givenArticle = {
Expand All @@ -41,23 +41,12 @@ describe('Comment', () => {

const postArticle = await chai.request(app).post('/api/articles').set('Authorization', token).send(givenArticle);
idArticle = postArticle.body.article.article_id;
await chai.request(app).post(`/api/articles/${idArticle}/comments`).set('Authorization', token2).send(givenComment);
await chai.request(app).post(`/api/articles/${idArticle}/comments`).set('Authorization', token).send(givenComment);
} catch (error) {
throw error;
}
});

after((done) => {
Comment.destroy({
where: {},
truncate: true,
}).then((deletedComment) => {
done();
}).catch((error) => {
throw error;
});
});

it('Should not let the user comment an article without a token', (done) => {
chai.request(app)
.post(`/api/articles/${idArticle}/comments`)
Expand Down Expand Up @@ -204,16 +193,10 @@ describe('Comment', () => {
done();
});
}).timeout(100000);
it('Should let the user delete a comment', (done) => {
validComment = {
content: 'What makes you special',
articleId: idArticle,
author: userId
};
it('Should let the user get all comments', (done) => {
chai.request(app)
.delete(`/api/articles/${idArticle}/comments/${idComment}`)
.get(`/api/articles/${idArticle}/comments`)
.set('Authorization', token)
.send(validComment)
.end((err, res) => {
if (err) {
done(err);
Expand All @@ -222,10 +205,16 @@ describe('Comment', () => {
done();
});
});
it('Should let the user get all comments', (done) => {
it('Should let the user delete a comment', (done) => {
validComment = {
content: 'What makes you special',
articleId: idArticle,
author: userId
};
chai.request(app)
.get(`/api/articles/${idArticle}/comments`)
.delete(`/api/articles/${idArticle}/comments/${idComment}`)
.set('Authorization', token)
.send(validComment)
.end((err, res) => {
if (err) {
done(err);
Expand Down
Loading

0 comments on commit 0ebef45

Please sign in to comment.