Skip to content

Commit

Permalink
Add support for resending verification email in case of expired token (
Browse files Browse the repository at this point in the history
…#3617)

* -Defines new public API route /apps/:appId/resend_verification_email that will generate a new email verification link and email for a user identified by username in POST body
-Add template and url support for invalidVerificationLink, linkSendSuccess, and linkSendFail pages. The invalidVerificationLink pages includes a button that allows the user to generate a new verification email if their current token has expired, using the new public API route
-All three pages have default html that will be functional out of the box, but they can be customized in the customPages object. The custom page for invalidVerificationLink needs to handle the extraction of the username and appId from the url and the POST to generate the new link (this requires javascript)
-Clicking a link for an email that has already been verified now routes to the emailVerifySuccess page instead of the invalidLink page

* Fix package.json repo url to be parse-server againwq

* Fix js lint issues

* Update unit tests

* Use arrow functions, change html page comments, use qs and a string template to construct location for invalidVerificationLink page, syntax fixes

* Remember to pass result when using arrow function
  • Loading branch information
cmmills91 authored and flovilmart committed May 10, 2017
1 parent 7b9ebc4 commit 22ba398
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 13 deletions.
2 changes: 2 additions & 0 deletions public_html/invalid_link.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
padding: 0 0 0 0;
}
</style>
</head>

<body>
<div class="container">
<h1>Invalid Link</h1>
Expand Down
68 changes: 68 additions & 0 deletions public_html/invalid_verification_link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email or reset password link
but their security token is wrong. This can either mean the user has clicked on a
stale link (i.e. re-click on a password reset link after resetting their password) or
(rarely) this could be a sign of a malicious user trying to tamper with your app.
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}

h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>
<script type="text/javascript">
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

window.onload = addDataToForm;

function addDataToForm() {
var username = getUrlParameter("username");
document.getElementById("usernameField").value = username;

var appId = getUrlParameter("appId");
document.getElementById("resendForm").action = '/apps/' + appId + '/resend_verification_email'
}

</script>

<body>
<div class="container">
<h1>Invalid Verification Link</h1>
<form id="resendForm" method="POST" action="/resend_verification_email">
<input id="usernameField" class="form-control" name="username" type="hidden" value="">
<button type="submit" class="btn btn-default">Resend Link</button>
</form>
</div>
</body>
</html>
45 changes: 45 additions & 0 deletions public_html/link_send_fail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email link with an invalid
security token and requests a link resend. This page is displayed when the username from
the original link is invalid or if the email of that user has already been verfieid when
the resend request is made
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}

h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>

<body>
<div class="container">
<h1>No link sent. User not found or email already verified</h1>
</div>
</body>
</html>
45 changes: 45 additions & 0 deletions public_html/link_send_success.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<!-- This page is displayed when someone navigates to a verify email link with an invalid
security token and requests a link resend. This page is displayed when the username
from the original verification link has been found and a new verification link has
been successfully sent to the corresponding stored email
-->
<html>
<head>
<title>Invalid Link</title>
<style type='text/css'>
.container {
border-width: 0px;
display: block;
font: inherit;
font-family: 'Helvetica Neue', Helvetica;
font-size: 16px;
height: 30px;
line-height: 16px;
margin: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
position: relative;
vertical-align: baseline;
}

h1, h2, h3, h4, h5 {
color: #0067AB;
display: block;
font: inherit;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 0 0 15px 0;
padding: 0 0 0 0;
}
</style>
</head>

<body>
<div class="container">
<h1>Link Sent! Check your email.</h1>
</div>
</body>
</html>
12 changes: 6 additions & 6 deletions spec/EmailVerificationToken.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Config = require('../src/Config');

describe("Email Verification Token Expiration: ", () => {

it('show the invalid link page, if the user clicks on the verify email link after the email verify token expires', done => {
it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
Expand Down Expand Up @@ -37,7 +37,7 @@ describe("Email Verification Token Expiration: ", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
done();
});
}, 1000);
Expand Down Expand Up @@ -313,7 +313,7 @@ describe("Email Verification Token Expiration: ", () => {
});
});

it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
Expand Down Expand Up @@ -359,7 +359,7 @@ describe("Email Verification Token Expiration: ", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity');
done();
});
})
Expand All @@ -369,7 +369,7 @@ describe("Email Verification Token Expiration: ", () => {
});
});

it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => {
it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => {
var user = new Parse.User();
var sendEmailOptions;
var emailAdapter = {
Expand Down Expand Up @@ -409,7 +409,7 @@ describe("Email Verification Token Expiration: ", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test');
done();
});
})
Expand Down
31 changes: 28 additions & 3 deletions spec/ValidationAndPasswordsReset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
});
});

it('redirects you to invalid link if you try to validate a nonexistant users email', done => {
it('redirects you to invalid verification link page if you try to validate a nonexistant users email', done => {
reconfigureServer({
appName: 'emailing app',
verifyUserEmails: true,
Expand All @@ -671,7 +671,32 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=sadfasga&appId=test');
done();
});
});
});

it('redirects you to link send fail page if you try to resend a link for a nonexistant user', done => {
reconfigureServer({
appName: 'emailing app',
verifyUserEmails: true,
emailAdapter: {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {}
},
publicServerURL: "http://localhost:8378/1"
})
.then(() => {
request.post('http://localhost:8378/1/apps/test/resend_verification_email', {
followRedirect: false,
form: {
username: "sadfasga"
}
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/link_send_fail.html');
done();
});
});
Expand All @@ -685,7 +710,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=zxcv&appId=test');
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
Expand Down
12 changes: 12 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@ export class Config {
return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`;
}

get invalidVerificationLinkURL() {
return this.customPages.invalidVerificationLink || `${this.publicServerURL}/apps/invalid_verification_link.html`;
}

get linkSendSuccessURL() {
return this.customPages.linkSendSuccess || `${this.publicServerURL}/apps/link_send_success.html`
}

get linkSendFailURL() {
return this.customPages.linkSendFail || `${this.publicServerURL}/apps/link_send_fail.html`
}

get verifyEmailSuccessURL() {
return this.customPages.verifyEmailSuccess || `${this.publicServerURL}/apps/verify_email_success.html`;
}
Expand Down
26 changes: 22 additions & 4 deletions src/Controllers/UserController.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,17 @@ export class UserController extends AdaptableController {
updateFields._email_verify_token_expires_at = {__op: 'Delete'};
}

return this.config.database.update('_User', query, updateFields).then((document) => {
if (!document) {
throw undefined;
var checkIfAlreadyVerified = new RestQuery(this.config, Auth.master(this.config), '_User', {username: username, emailVerified: true});
return checkIfAlreadyVerified.execute().then(result => {
if (result.results.length) {
return Promise.resolve(result.results.length[0]);
}
return Promise.resolve(document);
return this.config.database.update('_User', query, updateFields).then((document) => {
if (!document) {
throw undefined
}
return Promise.resolve(document);
})
});
}

Expand Down Expand Up @@ -134,6 +140,18 @@ export class UserController extends AdaptableController {
});
}

resendVerificationEmail(username) {
return this.getUserIfNeeded({username: username}).then((aUser) => {
if (!aUser || aUser.emailVerified) {
throw undefined;
}
this.setEmailVerifyToken(aUser);
return this.config.database.update('_User', {username}, aUser).then(() => {
this.sendVerificationEmail(aUser);
});
});
}

setPasswordResetToken(email) {
const token = { _perishable_token: randomString(25) };

Expand Down
Loading

0 comments on commit 22ba398

Please sign in to comment.