Skip to content

Commit

Permalink
Refactors verify_email, adds public html
Browse files Browse the repository at this point in the history
  • Loading branch information
flovilmart committed Feb 29, 2016
1 parent 0b307bc commit 7dd7652
Show file tree
Hide file tree
Showing 14 changed files with 455 additions and 63 deletions.
175 changes: 175 additions & 0 deletions public_html/choose_password.html
@@ -0,0 +1,175 @@
<!DOCTYPE html>
<html>
<!-- This page is displayed when someone clicks a valid 'reset password' link.
Users should feel free to add to this page (i.e. branding or security widgets)
but should be sure not to delete any of the form inputs or the javascript from the
template file. This javascript is what adds the necessary values to authenticate
this session with Parse.
The query params 'username' and 'app' hold the friendly names for your current user and
your app. You should feel free to incorporate their values to make the page more personal.
If you are missing form parameters in your POST, Parse will navigate back to this page and
add an 'error' query parameter.
-->
<head>
<title>Password Reset</title>
<style type='text/css'>
h1 {
display: block;
font: inherit;
font-size: 30px;
font-weight: 600;
height: 30px;
line-height: 30px;
margin: 45px 0px 45px 0px;
padding: 0px 8px 0px 8px;
}

.error {
color: red;
padding: 0px 8px 0px 8px;
margin: -25px 0px -20px 0px;
}

body {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica;
color: #0067AB;
margin: 15px 99px 0px 98px;
}

label {
color: #666666;
}
form {
margin: 0px 0px 45px 0px;
padding: 0px 8px 0px 8px;
}
form > * {
display: block;
margin-top: 25px;
margin-bottom: 7px;
}

button {
font-size: 22px;
color: white;
background: #0067AB;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
-o-border-radius: 5px;
-ms-border-radius: 5px;
-khtml-border-radius: 5px;
border-radius: 5px;
background-image: -webkit-gradient(linear,50% 0,50% 100%,color-stop(0%,#0070BA),color-stop(100%,#00558C));
background-image: -webkit-linear-gradient(#0070BA,#00558C);
background-image: -moz-linear-gradient(#0070BA,#00558C);
background-image: -o-linear-gradient(#0070BA,#00558C);
background-image: -ms-linear-gradient(#0070BA,#00558C);
background-image: linear-gradient(#0070BA,#00558C);
-moz-box-shadow: inset 0 1px 0 0 #0076c4;
-webkit-box-shadow: inset 0 1px 0 0 #0076c4;
-o-box-shadow: inset 0 1px 0 0 #0076c4;
box-shadow: inset 0 1px 0 0 #0076c4;
border: 1px solid #005E9C;
padding: 10px 14px;
cursor: pointer;
outline: none;
display: block;
font-family: "Helvetica Neue",Helvetica;

-webkit-box-align: center;
text-align: center;
box-sizing: border-box;
letter-spacing: normal;
word-spacing: normal;
line-height: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
}

button:hover {
background-image: -webkit-gradient(linear,50% 0,50% 100%,color-stop(0%,#0079CA),color-stop(100%,#005E9C));
background-image: -webkit-linear-gradient(#0079CA,#005E9C);
background-image: -moz-linear-gradient(#0079CA,#005E9C);
background-image: -o-linear-gradient(#0079CA,#005E9C);
background-image: -ms-linear-gradient(#0079CA,#005E9C);
background-image: linear-gradient(#0079CA,#005E9C);
-moz-box-shadow: inset 0 0 0 0 #0076c4;
-webkit-box-shadow: inset 0 0 0 0 #0076c4;
-o-box-shadow: inset 0 0 0 0 #0076c4;
box-shadow: inset 0 0 0 0 #0076c4;
}

button:active {
background-image: -webkit-gradient(linear,50% 0,50% 100%,color-stop(0%,#00395E),color-stop(100%,#005891));
background-image: -webkit-linear-gradient(#00395E,#005891);
background-image: -moz-linear-gradient(#00395E,#005891);
background-image: -o-linear-gradient(#00395E,#005891);
background-image: -ms-linear-gradient(#00395E,#005891);
background-image: linear-gradient(#00395E,#005891);
}

input {
color: black;
cursor: auto;
display: inline-block;
font-family: 'Helvetica Neue', Helvetica;
font-size: 25px;
height: 30px;
letter-spacing: normal;
line-height: normal;
margin: 2px 0px 2px 0px;
padding: 5px;
text-transform: none;
vertical-align: baseline;
width: 500px;
word-spacing: 0px;
}

</style>
</head>
<body>
<h1>Reset Your Password<span id='app'></span></h1>
<noscript>We apologize, but resetting your password requires javascript</noscript>
<div class='error' id='error'></div>
<form id='form' action='#' method='POST'>
<label>New Password for <span id='username_label'></span></label>
<input name="new_password" type="password" />
<input name='utf-8' type='hidden' value='' />
<input name="username" id="username" type="hidden" />
<input name="token" id="token" type="hidden" />
<button>Change Password</button>
</form>

<script language='javascript' type='text/javascript'>
<!--
window.onload = function() {
var urlParams = {};
(function () {
var pair, // Really a match. Index 0 is the full match; 1 & 2 are the key & val.
tokenize = /([^&=]+)=?([^&]*)/g,
// decodeURIComponents escapes everything but will leave +s that should be ' '
re_space = function (s) { return decodeURIComponent(s.replace(/\+/g, " ")); },
// Substring to cut off the leading '?'
querystring = window.location.search.substring(1);

while (pair = tokenize.exec(querystring))
urlParams[re_space(pair[1])] = re_space(pair[2]);
})();

var id = urlParams['id'];
document.getElementById('form').setAttribute('action', '/apps/' + id + '/request_password_reset');
document.getElementById('username').value = urlParams['username'];
document.getElementById('username_label').appendChild(document.createTextNode(urlParams['username']));

document.getElementById('token').value = urlParams['token'];
if (urlParams['error']) {
document.getElementById('error').appendChild(document.createTextNode(urlParams['error']));
}
if (urlParams['app']) {
document.getElementById('app').appendChild(document.createTextNode(' for ' + urlParams['app']));
}
}
//-->
</script>
</body>
43 changes: 43 additions & 0 deletions public_html/invalid_link.html
@@ -0,0 +1,43 @@
<!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>
<body>
<div class="container">
<h1>Invalid Link</h1>
</div>
</body>
</html>
27 changes: 27 additions & 0 deletions public_html/password_reset_success.html
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!-- This page is displayed whenever someone has successfully reset their password.
Pro and Enterprise accounts may edit this page and tell Parse to use that custom
version in their Parse app. See the App Settigns page for more information.
This page will be called with the query param 'username'
-->
<head>
<title>Password Reset</title>
<style type='text/css'>
h1 {
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: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
}
</style>
<body>
<h1>Successfully updated your password!</h1>
</body>
</html>
27 changes: 27 additions & 0 deletions public_html/verify_email_success.html
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<!-- This page is displayed whenever someone has successfully reset their password.
Pro and Enterprise accounts may edit this page and tell Parse to use that custom
version in their Parse app. See the App Settigns page for more information.
This page will be called with the query param 'username'
-->
<head>
<title>Email Verification</title>
<style type='text/css'>
h1 {
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: 45px 0px 0px 45px;
padding: 0px 8px 0px 8px;
}
</style>
<body>
<h1>Successfully verified your email!</h1>
</body>
</html>
14 changes: 7 additions & 7 deletions spec/ParseUser.spec.js
Expand Up @@ -171,7 +171,7 @@ describe('Parse.User testing', () => {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/verify_email_success.html?username=zxcv');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=zxcv');
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(true);
Expand Down Expand Up @@ -202,21 +202,21 @@ describe('Parse.User testing', () => {
});

it('redirects you to invalid link if you try to verify email incorrecly', done => {
request.get('http://localhost:8378/1/verify_email', {
request.get('http://localhost:8378/1/apps/test/verify_email', {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
done()
});
});

it('redirects you to invalid link if you try to validate a nonexistant users email', done => {
request.get('http://localhost:8378/1/verify_email?token=asdfasdf&username=sadfasga', {
request.get('http://localhost:8378/1/apps/test/verify_email?token=asdfasdf&username=sadfasga', {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
done();
});
});
Expand All @@ -225,11 +225,11 @@ describe('Parse.User testing', () => {
var user = new Parse.User();
var emailAdapter = {
sendVerificationEmail: options => {
request.get('http://localhost:8378/1/verify_email?token=invalid&username=zxcv', {
request.get('http://localhost:8378/1/apps/test/verify_email?token=invalid&username=zxcv', {
followRedirect: false,
}, (error, response, body) => {
expect(response.statusCode).toEqual(302);
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/invalid_link.html');
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html');
user.fetch()
.then(() => {
expect(user.get('emailVerified')).toEqual(false);
Expand Down
25 changes: 24 additions & 1 deletion src/Config.js
Expand Up @@ -25,18 +25,41 @@ export class Config {
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);

this.mailController = cacheInfo.mailController;

this.serverURL = cacheInfo.serverURL;
this.verifyUserEmails = cacheInfo.verifyUserEmails;
this.appName = cacheInfo.appName;

this.hooksController = cacheInfo.hooksController;
this.filesController = cacheInfo.filesController;
this.pushController = cacheInfo.pushController;
this.loggerController = cacheInfo.loggerController;
this.mailController = cacheInfo.mailController;
this.oauth = cacheInfo.oauth;

this.mount = mount;
}
}

get invalidLinkURL() {
return `${this.serverURL}/apps/invalid_link.html`;
}

get verifyEmailSuccessURL() {
return `${this.serverURL}/apps/verify_email_success.html`;
}

get choosePasswordURL() {
return `${this.serverURL}/apps/choose_password`;
}

get passwordResetSuccessURL() {
return `${this.serverURL}/apps/password_reset_success.html`;
}

get verifyEmailURL() {
return `${this.serverURL}/apps/${this.applicationId}/verify_email`;
}
};

export default Config;
module.exports = Config;
8 changes: 7 additions & 1 deletion src/Controllers/AdaptableController.js
Expand Up @@ -10,11 +10,13 @@ based on the parameters passed

// _adapter is private, use Symbol
var _adapter = Symbol();
import cache from '../cache';

export class AdaptableController {

constructor(adapter) {
constructor(adapter, appId) {
this.adapter = adapter;
this.appId = appId;
}

set adapter(adapter) {
Expand All @@ -26,6 +28,10 @@ export class AdaptableController {
return this[_adapter];
}

get config() {
return cache.apps[this.appId];
}

expectedAdapterType() {
throw new Error("Subclasses should implement expectedAdapterType()");
}
Expand Down

0 comments on commit 7dd7652

Please sign in to comment.