Merge branch 'issue-121-password-reset-screens' of github.com:mozilla/fxa-content-server into shane-pwreset
This commit is contained in:
Коммит
45963b8e6d
|
@ -13,6 +13,25 @@ define([
|
|||
'processed/constants'
|
||||
],
|
||||
function (FxaClient, Constants) {
|
||||
// placeholder promise to stand in for FxaClient functionality that is
|
||||
// not yet ready.
|
||||
function PromiseMock() {
|
||||
}
|
||||
PromiseMock.prototype = {
|
||||
then: function (callback) {
|
||||
var promise = new PromiseMock();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
done: function (callback) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function FxaClientWrapper() {
|
||||
this.client = new FxaClient(Constants.FXA_ACCOUNT_SERVER);
|
||||
}
|
||||
|
@ -33,6 +52,14 @@ function (FxaClient, Constants) {
|
|||
|
||||
verifyCode: function (uid, code) {
|
||||
return this.client.verifyCode(uid, code);
|
||||
},
|
||||
|
||||
requestPasswordReset: function () {
|
||||
return new PromiseMock();
|
||||
},
|
||||
|
||||
completePasswordReset: function () {
|
||||
return new PromiseMock();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// utilities to deal with urls
|
||||
'use strict';
|
||||
|
||||
define(['underscore'],
|
||||
function (_) {
|
||||
return {
|
||||
searchParam: function (name, str) {
|
||||
var search = (str || window.location.search).replace(/^\?/, '');
|
||||
if (! search) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pairs = search.split('&');
|
||||
var terms = {};
|
||||
|
||||
_.each(pairs, function (pair) {
|
||||
var keyValue = pair.split('=');
|
||||
terms[keyValue[0]] = decodeURIComponent(keyValue[1]);
|
||||
});
|
||||
|
||||
return terms[name];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Basic XSS protection
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'processed/constants'
|
||||
],
|
||||
function(_, Constants) {
|
||||
return {
|
||||
// only allow http or https URLs, encoding the URL.
|
||||
href: function(text) {
|
||||
if (! _.isString(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! /^https?:\/\//.test(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var encodedURI = encodeURI(text);
|
||||
|
||||
// All browsers have a max length of URI that they can handle.
|
||||
// IE8 has the shortest total length at 2083 bytes and 2048 characters
|
||||
// for GET requests.
|
||||
// See http://support.microsoft.com/kb/q208427
|
||||
|
||||
// Check the total encoded URI length
|
||||
if (encodedURI.length > Constants.URL_MAX_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
return encodedURI;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -6,7 +6,13 @@
|
|||
|
||||
define([], function () {
|
||||
return {
|
||||
FXA_ACCOUNT_SERVER: '/* @echo fxaccountUrl */'
|
||||
FXA_ACCOUNT_SERVER: '/* @echo fxaccountUrl */',
|
||||
|
||||
// All browsers have a max length of URI that they can handle.
|
||||
// IE8 has the shortest total length at 2083 bytes and 2048 characters
|
||||
// for GET requests.
|
||||
// See http://support.microsoft.com/kb/q208427
|
||||
URL_MAX_LENGTH: 2048
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -19,9 +19,13 @@ define([
|
|||
'views/create_account',
|
||||
'views/cannot_create_account',
|
||||
'views/complete_sign_up',
|
||||
'views/reset_password',
|
||||
'views/confirm_reset_password',
|
||||
'views/complete_reset_password',
|
||||
'views/reset_password_complete',
|
||||
'transit'
|
||||
],
|
||||
function ($, Backbone, IntroView, SignInView, SignUpView, ConfirmView, SettingsView, TosView, PpView, AgeView, BirthdayView, CreateAccountView, CannotCreateAccountView, CompleteSignUpView) {
|
||||
function ($, Backbone, IntroView, SignInView, SignUpView, ConfirmView, SettingsView, TosView, PpView, AgeView, BirthdayView, CreateAccountView, CannotCreateAccountView, CompleteSignUpView, ResetPasswordView, ConfirmResetPasswordView, CompleteResetPasswordView, ResetPasswordCompleteView) {
|
||||
var Router = Backbone.Router.extend({
|
||||
routes: {
|
||||
'': 'showIntro',
|
||||
|
@ -35,7 +39,11 @@ function ($, Backbone, IntroView, SignInView, SignUpView, ConfirmView, SettingsV
|
|||
'birthday': 'showBirthday',
|
||||
'create_account': 'showCreateAccount',
|
||||
'cannot_create_account': 'showCannotCreateAccount',
|
||||
'verify_email': 'showCompleteSignUp'
|
||||
'verify_email': 'showCompleteSignUp',
|
||||
'reset_password': 'showResetPassword',
|
||||
'confirm_reset_password': 'showConfirmResetPassword',
|
||||
'complete_reset_password': 'showCompleteResetPassword',
|
||||
'reset_password_complete': 'showResetPasswordComplete'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
|
@ -95,6 +103,22 @@ function ($, Backbone, IntroView, SignInView, SignUpView, ConfirmView, SettingsV
|
|||
this.showView(new CompleteSignUpView());
|
||||
},
|
||||
|
||||
showResetPassword: function () {
|
||||
this.showView(new ResetPasswordView());
|
||||
},
|
||||
|
||||
showConfirmResetPassword: function () {
|
||||
this.showView(new ConfirmResetPasswordView());
|
||||
},
|
||||
|
||||
showCompleteResetPassword: function () {
|
||||
this.showView(new CompleteResetPasswordView());
|
||||
},
|
||||
|
||||
showResetPasswordComplete: function () {
|
||||
this.showView(new ResetPasswordCompleteView());
|
||||
},
|
||||
|
||||
showView: function (view) {
|
||||
if (this.currentView) {
|
||||
this.currentView.destroy();
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<header>
|
||||
<h1 id='fxa-complete-reset-password-header'>{{#t}}Firefox Accounts{{/t}}</h1>
|
||||
|
||||
<h2>{{#t}}Please create a new password{{/t}}</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="error"></div>
|
||||
|
||||
<form>
|
||||
<div class="input-row">
|
||||
<input type="password" class="password" id="password" placeholder="{{#t}}Password{{/t}}" pattern=".{8,}">
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<input type="password" class="password" id="vpassword" placeholder="{{#t}}Repeat Password{{/t}}" pattern=".{8,}">
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button type="submit">{{#t}}Next >{{/t}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<header>
|
||||
<h1 id="fxa-confirm-reset-password-header">{{#t}}Firefox Accounts{{/t}}</h1>
|
||||
|
||||
<h2>{{#t}}Password Reset Email Sent{{/t}}</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="error"></div>
|
||||
|
||||
<div class="placeholder email-placeholder">
|
||||
{{#t}}Email{{/t}}
|
||||
</div>
|
||||
|
||||
<p>{{#t}}Your password reset link awates at:{{/t}}
|
||||
<br/>
|
||||
<strong>{{email}}.</strong>
|
||||
</p>
|
||||
|
||||
<div class="links">
|
||||
{{#t}}Email not arriving?{{/t}} <a>{{#t}}Send again{{/t}}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
</footer>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<header>
|
||||
<h1 id='fxa-reset-password-header'>{{#t}}Firefox Accounts{{/t}}</h1>
|
||||
|
||||
<h2>{{#t}}Reset Password{{/t}}</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="error"></div>
|
||||
|
||||
<form>
|
||||
<label for="email">
|
||||
{{#t}}Enter your email address, and we'll email you instructions on how to reset your password{{/t}}
|
||||
</label>
|
||||
|
||||
<div class="input-row">
|
||||
<input name="email" type="email" class="email" placeholder="{{#t}}Email{{/t}}">
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button type="submit">{{#t}}Submit{{/t}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="links">
|
||||
<a href="/signin">{{#t}}Sign In{{/t}}</a>
|
||||
•
|
||||
<a href="/signup">{{#t}}Create Account{{/t}}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
</footer>
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<header>
|
||||
<h1 id='fxa-reset-password-complete-header'>{{#t}}Firefox Accounts{{/t}}</h1>
|
||||
|
||||
<h2>{{#t}}Password Reset!{{/t}}</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="error"></div>
|
||||
|
||||
|
||||
{{#redirectTo}}
|
||||
<p>
|
||||
{{#t}}Continue to{{/t}} {{ service }} {{#t}}on the <strong>Initial Client</strong>.{{/t}}
|
||||
</p>
|
||||
|
||||
<a href="{{ redirectTo }}">Continue</a>
|
||||
{{/redirectTo}}
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
</footer>
|
||||
|
|
@ -22,10 +22,11 @@
|
|||
</form>
|
||||
|
||||
<div class="links">
|
||||
<a href="">{{#t}}Forgot Password{{/t}}</a> • <a href="/signup">{{#t}}Create Account{{/t}}</a>
|
||||
<a href="/reset_password">{{#t}}Forgot Password{{/t}}</a>
|
||||
•
|
||||
<a href="/signup">{{#t}}Create Account{{/t}}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
|
|
@ -86,6 +86,17 @@ function(_, Backbone) {
|
|||
_.invoke(this.subviews, 'destroy');
|
||||
|
||||
this.subviews = [];
|
||||
},
|
||||
|
||||
isElementValid: function (selector) {
|
||||
var el = this.$(selector);
|
||||
var value = el.val();
|
||||
return value && el[0].validity.valid;
|
||||
},
|
||||
|
||||
displayError: function(msg) {
|
||||
// TODO - run the error message through the translator
|
||||
this.$('.error').html(msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'views/base',
|
||||
'stache!templates/complete_reset_password',
|
||||
'lib/fxa-client',
|
||||
'lib/session',
|
||||
'lib/url'
|
||||
],
|
||||
function (_, BaseView, Template, FxaClient, Session, Url) {
|
||||
var View = BaseView.extend({
|
||||
template: Template,
|
||||
className: 'complete_reset_password',
|
||||
|
||||
events: {
|
||||
'submit form': 'submit'
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
this.uid = Url.searchParam('uid');
|
||||
if (! this.uid) {
|
||||
return this.displayError('no uid specified');
|
||||
}
|
||||
|
||||
this.code = Url.searchParam('code');
|
||||
if (! this.code) {
|
||||
return this.displayError('no code specified');
|
||||
}
|
||||
},
|
||||
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (! (this.uid &&
|
||||
this.code &&
|
||||
this._validatePasswords())) {
|
||||
return;
|
||||
}
|
||||
|
||||
var password = this._getPassword();
|
||||
|
||||
var client = new FxaClient();
|
||||
client.completePasswordReset(password, this.uid, this.code)
|
||||
.done(_.bind(this._onResetCompleteSuccess, this),
|
||||
_.bind(this._onResetCompleteFailure, this));
|
||||
},
|
||||
|
||||
_onResetCompleteSuccess: function () {
|
||||
// This information will be displayed on the
|
||||
// reset_password_complete screen.
|
||||
Session.service = Url.searchParam('service');
|
||||
Session.redirectTo = Url.searchParam('redirectTo');
|
||||
router.navigate('reset_password_complete', { trigger: true });
|
||||
},
|
||||
|
||||
_onResetCompleteFailure: function (err) {
|
||||
this.displayError(err.message);
|
||||
},
|
||||
|
||||
_validatePasswords: function () {
|
||||
if (! (this.isElementValid('#password') &&
|
||||
this.isElementValid('#vpassword'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._getPassword() !== this._getVPassword()) {
|
||||
this.displayError('passwords do not match');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_getPassword: function () {
|
||||
return this.$('#password').val();
|
||||
},
|
||||
|
||||
_getVPassword: function () {
|
||||
return this.$('#vpassword').val();
|
||||
}
|
||||
});
|
||||
|
||||
return View;
|
||||
});
|
|
@ -9,26 +9,11 @@ define([
|
|||
'views/base',
|
||||
'stache!templates/complete_sign_up',
|
||||
'lib/session',
|
||||
'lib/fxa-client'
|
||||
'lib/fxa-client',
|
||||
'lib/url',
|
||||
'lib/xss'
|
||||
],
|
||||
function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient) {
|
||||
function getSearchParam(name) {
|
||||
var search = window.location.search.replace(/^\?/, '');
|
||||
if (! search) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pairs = search.split('&');
|
||||
var terms = {};
|
||||
|
||||
_.each(pairs, function (pair) {
|
||||
var keyValue = pair.split('=');
|
||||
terms[keyValue[0]] = keyValue[1];
|
||||
});
|
||||
|
||||
return terms[name];
|
||||
}
|
||||
|
||||
function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient, Url, Xss) {
|
||||
var CompleteSignUpView = BaseView.extend({
|
||||
template: CompleteSignUpTemplate,
|
||||
className: 'complete_sign_up',
|
||||
|
@ -36,34 +21,32 @@ function (_, BaseView, CompleteSignUpTemplate, Session, FxaClient) {
|
|||
context: function () {
|
||||
return {
|
||||
email: Session.email,
|
||||
siteName: getSearchParam('service'),
|
||||
redirectTo: getSearchParam('service')
|
||||
service: Url.searchParam('service'),
|
||||
redirectTo: Xss.href(Url.searchParam('redirectTo'))
|
||||
};
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
var uid = getSearchParam('uid');
|
||||
var uid = Url.searchParam('uid');
|
||||
if (! uid) {
|
||||
return this._displayError('no uid specified');
|
||||
return this.displayError('no uid specified');
|
||||
}
|
||||
|
||||
var code = getSearchParam('code');
|
||||
var code = Url.searchParam('code');
|
||||
if (! code) {
|
||||
return this._displayError('no code specified');
|
||||
return this.displayError('no code specified');
|
||||
}
|
||||
|
||||
var client = new FxaClient();
|
||||
client.verifyCode(uid, code)
|
||||
.then(function () {
|
||||
// TODO - we could go to a "sign_up_complete" screen here.
|
||||
this.$('#fxa-complete-sign-up-success').show();
|
||||
}.bind(this), function (err) {
|
||||
this._displayError(err.message);
|
||||
this.displayError(err.message);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_displayError: function (msg) {
|
||||
this.$('.error').html(msg);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return CompleteSignUpView;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'views/base',
|
||||
'stache!templates/confirm_reset_password',
|
||||
'lib/session'
|
||||
],
|
||||
function (BaseView, Template, Session) {
|
||||
var View = BaseView.extend({
|
||||
template: Template,
|
||||
className: 'confirm-reset-password',
|
||||
|
||||
context: function () {
|
||||
return {
|
||||
email: Session.email
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return View;
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'views/base',
|
||||
'stache!templates/reset_password',
|
||||
'lib/fxa-client',
|
||||
'lib/session'
|
||||
],
|
||||
function (_, BaseView, Template, FxaClient, Session) {
|
||||
var View = BaseView.extend({
|
||||
template: Template,
|
||||
className: 'reset_password',
|
||||
|
||||
events: {
|
||||
'submit form': 'requestPasswordReset'
|
||||
},
|
||||
|
||||
requestPasswordReset: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (! this._validateEmail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var email = this._getEmail();
|
||||
|
||||
var client = new FxaClient();
|
||||
client.requestPasswordReset(email)
|
||||
.done(this._onRequestResetSuccess.bind(this),
|
||||
this._onRequestResetFailure.bind(this));
|
||||
|
||||
},
|
||||
|
||||
_onRequestResetSuccess: function () {
|
||||
Session.email = this._getEmail();
|
||||
router.navigate('confirm_reset_password', { trigger: true });
|
||||
},
|
||||
|
||||
_onRequestResetFailure: function (err) {
|
||||
this.displayError(err.message);
|
||||
},
|
||||
|
||||
_getEmail: function () {
|
||||
return this.$('.email').val();
|
||||
},
|
||||
|
||||
_validateEmail: function () {
|
||||
return this.isElementValid('.email');
|
||||
}
|
||||
});
|
||||
|
||||
return View;
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'views/base',
|
||||
'stache!templates/reset_password_complete',
|
||||
'lib/session',
|
||||
'lib/xss'
|
||||
],
|
||||
function (_, BaseView, Template, Session, Xss) {
|
||||
var View = BaseView.extend({
|
||||
template: Template,
|
||||
className: 'reset_password_complete',
|
||||
|
||||
context: function () {
|
||||
return {
|
||||
email: Session.email,
|
||||
service: Session.service,
|
||||
redirectTo: Xss.href(Session.redirectTo)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return View;
|
||||
});
|
|
@ -32,17 +32,11 @@ function (BaseView, SignUpTemplate, Session) {
|
|||
},
|
||||
|
||||
_validateEmail: function () {
|
||||
return this._isElementValid('.email');
|
||||
return this.isElementValid('.email');
|
||||
},
|
||||
|
||||
_validatePassword: function () {
|
||||
return this._isElementValid('.password');
|
||||
},
|
||||
|
||||
_isElementValid: function (selector) {
|
||||
var el = this.$(selector);
|
||||
var value = el.val();
|
||||
return value && el[0].validity.valid;
|
||||
return this.isElementValid('.password');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -48,7 +48,9 @@ require.config({
|
|||
require([
|
||||
'mocha',
|
||||
'../tests/setup',
|
||||
'../tests/spec/lib/channels/fx-desktop'
|
||||
'../tests/spec/lib/channels/fx-desktop',
|
||||
'../tests/spec/lib/xss',
|
||||
'../tests/spec/lib/url'
|
||||
],
|
||||
function (Mocha) {
|
||||
var runner = Mocha.run();
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
define([
|
||||
'mocha',
|
||||
'chai',
|
||||
'underscore',
|
||||
'lib/url',
|
||||
'processed/constants'
|
||||
],
|
||||
function (mocha, chai, _, Url, Constants) {
|
||||
var assert = chai.assert;
|
||||
var channel;
|
||||
|
||||
describe('lib/url', function () {
|
||||
describe('searchParam', function () {
|
||||
it('returns a parameter from window.location.serach, if it exists',
|
||||
function() {
|
||||
assert.equal(Url.searchParam('color', '?color=green'), 'green');
|
||||
});
|
||||
|
||||
it('returns undefined if parameter does not exist', function() {
|
||||
assert.isUndefined(Url.searchParam('animal', '?color=green'));
|
||||
});
|
||||
|
||||
it('does not throw if str override is not specified', function() {
|
||||
assert.isUndefined(Url.searchParam('animal'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
define([
|
||||
'mocha',
|
||||
'chai',
|
||||
'underscore',
|
||||
'lib/xss',
|
||||
'processed/constants'
|
||||
],
|
||||
function (mocha, chai, _, XSS, Constants) {
|
||||
var assert = chai.assert;
|
||||
var channel;
|
||||
|
||||
describe('lib/xss', function () {
|
||||
describe('href', function () {
|
||||
function expectEmpty(url) {
|
||||
assert.isUndefined(XSS.href(url));
|
||||
}
|
||||
|
||||
it('allows http href', function () {
|
||||
assert.equal(XSS.href('http://all.good'), 'http://all.good');
|
||||
});
|
||||
|
||||
it('allows https href', function () {
|
||||
assert.equal(XSS.href('https://all.good'), 'https://all.good');
|
||||
});
|
||||
|
||||
it('allows href with query parameters', function () {
|
||||
assert.equal(XSS.href('https://all.good?with_query'),
|
||||
'https://all.good?with_query');
|
||||
});
|
||||
|
||||
it('allows but escapes URLs that try to break out', function () {
|
||||
assert.equal(XSS.href('http://href.gone.bad" onclick="javascript(1)"'),
|
||||
'http://href.gone.bad%22%20onclick=%22javascript(1)%22');
|
||||
});
|
||||
|
||||
it('disallows javascript: href', function () {
|
||||
expectEmpty('javascript:alert(1)');
|
||||
});
|
||||
|
||||
it('disallows href without a scheme', function () {
|
||||
expectEmpty('no.scheme');
|
||||
});
|
||||
|
||||
it('disallows relative scheme', function () {
|
||||
expectEmpty('//relative.scheme');
|
||||
});
|
||||
|
||||
it('disallows data URI scheme', function () {
|
||||
expectEmpty('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D');
|
||||
});
|
||||
|
||||
it('only allows strings', function () {
|
||||
var disallowedItems = [
|
||||
1,
|
||||
true,
|
||||
new Date(),
|
||||
{},
|
||||
[]
|
||||
];
|
||||
|
||||
_.each(disallowedItems, expectEmpty);
|
||||
});
|
||||
|
||||
it('allows hrefs of the max length', function() {
|
||||
var maxLength = Constants.URL_MAX_LENGTH;
|
||||
var allowed = "http://";
|
||||
|
||||
while (allowed.length < maxLength) {
|
||||
allowed += 'a';
|
||||
}
|
||||
|
||||
assert.equal(XSS.href(allowed), allowed);
|
||||
});
|
||||
|
||||
it('disallowed hrefs that are too long', function() {
|
||||
var maxLength = Constants.URL_MAX_LENGTH;
|
||||
var tooLong = "http://";
|
||||
|
||||
while (tooLong.length < maxLength + 1) {
|
||||
tooLong += 'a';
|
||||
}
|
||||
|
||||
expectEmpty(tooLong);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -11,6 +11,10 @@ define([
|
|||
'./functional/age',
|
||||
'./functional/birthday',
|
||||
'./functional/confirm',
|
||||
'./functional/reset_password',
|
||||
'./functional/confirm_reset_password',
|
||||
'./functional/complete_reset_password',
|
||||
'./functional/reset_password_complete',
|
||||
'./functional/mocha'
|
||||
], function () {
|
||||
'use strict';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define([
|
||||
'intern!object',
|
||||
'intern/chai!assert',
|
||||
'require'
|
||||
], function (registerSuite, assert, require) {
|
||||
'use strict';
|
||||
|
||||
var url = 'http://localhost:3030/complete_reset_password';
|
||||
|
||||
registerSuite({
|
||||
name: 'complete_reset_password',
|
||||
|
||||
setup: function () {
|
||||
},
|
||||
|
||||
'open page': function () {
|
||||
|
||||
return this.get('remote')
|
||||
.get(require.toUrl(url))
|
||||
.waitForElementById('fxa-complete-reset-password-header')
|
||||
|
||||
.end();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define([
|
||||
'intern!object',
|
||||
'intern/chai!assert',
|
||||
'require'
|
||||
], function (registerSuite, assert, require) {
|
||||
'use strict';
|
||||
|
||||
var PAGE_URL = 'http://localhost:3030/confirm_reset_password';
|
||||
|
||||
registerSuite({
|
||||
name: 'confirm_password_reset',
|
||||
|
||||
'open page': function () {
|
||||
|
||||
return this.get('remote')
|
||||
.get(require.toUrl(PAGE_URL))
|
||||
.waitForElementById('fxa-confirm-reset-password-header')
|
||||
|
||||
.end();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define([
|
||||
'intern!object',
|
||||
'intern/chai!assert',
|
||||
'require',
|
||||
'intern/node_modules/dojo/node!xmlhttprequest',
|
||||
'app/bower_components/fxa-js-client/fxa-client'
|
||||
], function (registerSuite, assert, require, nodeXMLHttpRequest, FxaClient) {
|
||||
'use strict';
|
||||
|
||||
var AUTH_SERVER_ROOT = 'http://127.0.0.1:9000/v1';
|
||||
var PAGE_URL = 'http://localhost:3030/reset_password';
|
||||
var PASSWORD = 'password';
|
||||
var email;
|
||||
|
||||
registerSuite({
|
||||
name: 'password_reset',
|
||||
|
||||
setup: function () {
|
||||
email = 'signin' + Math.random() + '@example.com';
|
||||
var client = new FxaClient(AUTH_SERVER_ROOT, {
|
||||
xhr: nodeXMLHttpRequest.XMLHttpRequest
|
||||
});
|
||||
return client.signUp(email, PASSWORD);
|
||||
},
|
||||
|
||||
'open page': function () {
|
||||
|
||||
return this.get('remote')
|
||||
.get(require.toUrl(PAGE_URL))
|
||||
.waitForElementById('fxa-reset-password-header')
|
||||
|
||||
.end();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define([
|
||||
'intern!object',
|
||||
'intern/chai!assert',
|
||||
'require'
|
||||
], function (registerSuite, assert, require) {
|
||||
'use strict';
|
||||
|
||||
var url = 'http://localhost:3030/reset_password_complete';
|
||||
|
||||
registerSuite({
|
||||
name: 'reset_password_complete',
|
||||
|
||||
setup: function () {
|
||||
},
|
||||
|
||||
'open email verification link': function () {
|
||||
|
||||
return this.get('remote')
|
||||
.get(require.toUrl(url))
|
||||
.waitForElementById('fxa-reset-password-complete-header')
|
||||
|
||||
.end();
|
||||
}
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче