Permalink
Browse files

fix(client): generate fresh flow id and begin time on sign-out

#4676

r=philbooth
  • Loading branch information...
1 parent 94acc26 commit 3ea1a9a4f8bd388cfb6571b03115f399c3115505 @philbooth philbooth committed on GitHub Jan 30, 2017
@@ -129,6 +129,10 @@ define(function (require, exports, module) {
},
onNavigate (event) {
+ if (event.server) {
+ return this.navigateAway(event.url);
+ }
+
this._nextViewModel = createViewModel(event.nextViewData);
this.navigate(event.url, event.routerOptions);
},
@@ -155,6 +159,20 @@ define(function (require, exports, module) {
return Backbone.Router.prototype.navigate.call(this, url, options);
},
+ /**
+ * Navigate externally to the application, flushing the metrics
+ * before doing so.
+ *
+ * @param {String} url
+ * @returns {Promise}
+ */
+ navigateAway (url) {
+ return this.metrics.flush()
+ .then(() => {
+ this.window.location.href = url;
+ });
+ },
+
navigateBack () {
this.window.history.back();
},
@@ -763,6 +763,18 @@ define(function (require, exports, module) {
},
/**
+ * Navigate externally to the application.
+ *
+ * @param {String} url
+ */
+ navigateAway (url) {
+ this.notifier.trigger('navigate', {
+ server: true,
+ url
+ });
+ },
+
+ /**
* Focus the element with the [autofocus] attribute, if not a touch device.
* Focusing an element on a touch device causes the virtual keyboard to
* be displayed, which hides part of the screen.
@@ -7,38 +7,37 @@
define(function (require, exports, module) {
'use strict';
- const BaseView = require('views/base');
const Notifier = require('lib/channels/notifier');
const Session = require('lib/session');
+ const Url = require('lib/url');
var Mixin = {
notifications: {
// populated below using event name aliases
},
clearSessionAndNavigateToSignIn () {
- // Unset uid otherwise it will henceforth be impossible
- // to sign in to different accounts inside this tab.
- this.relier.unset('uid');
- this.relier.unset('email');
this.user.clearSignedInAccountUid();
- if (this._formPrefill) {
- // The user has manually signed out, a pretty strong indicator
- // the user does not want any of their information pre-filled
- // on the signin page. Clear any remaining formPrefill info
- // to ensure their data isn't sticking around in memory.
- this._formPrefill.clear();
- }
Session.clear();
this.navigateToSignIn();
},
navigateToSignIn () {
- this.navigate('signin', {
- success: BaseView.t('Signed out successfully')
- }, {
- clearQueryParams: true
+ const queryString = Url.objToSearchString({
+ context: this.relier.get('context'),
+ entrypoint: this.relier.get('entrypoint'),
+ service: this.relier.get('service'),
+ /* eslint-disable camelcase */
+ utm_campaign: this.relier.get('utmCampaign'),
+ utm_content: this.relier.get('utmContent'),
+ utm_medium: this.relier.get('utmMedium'),
+ utm_source: this.relier.get('utmSource'),
+ utm_term: this.relier.get('utmTerm')
+ /* eslint-enable camelcase */
});
+ // Navigate via the back-end in order to regenerate
+ // flow id and flow begin time for the next session.
+ this.navigateAway(`/signin${queryString}`);
}
};
@@ -106,6 +106,45 @@ define(function (require, exports, module) {
});
});
+ describe('`navigate` notifier message with `server: true`', () => {
+ beforeEach(() => {
+ sinon.spy(router, 'navigate');
+ sinon.spy(router, 'navigateAway');
+
+ notifier.trigger('navigate', {
+ server: true,
+ url: 'wibble'
+ });
+ });
+
+ it('navigated correctly', () => {
+ assert.equal(router.navigate.callCount, 0);
+ assert.equal(router.navigateAway.callCount, 1);
+ const args = router.navigateAway.args[0];
+ assert.lengthOf(args, 1);
+ assert.equal(args[0], 'wibble');
+ });
+ });
+
+ describe('navigateAway', () => {
+ beforeEach(() => {
+ sinon.spy(metrics, 'flush');
+ sinon.spy(router, 'navigate');
+
+ return router.navigateAway('blee');
+ });
+
+ it('called metrics.flush correctly', () => {
+ assert.equal(metrics.flush.callCount, 1);
+ assert.lengthOf(metrics.flush.args[0], 0);
+ });
+
+ it('navigated correctly', function () {
+ assert.equal(router.navigate.callCount, 0);
+ assert.equal(windowMock.location.href, 'blee');
+ });
+ });
+
describe('`navigate-back` notifier message', function () {
beforeEach(function () {
sinon.spy(router, 'navigateBack');
@@ -657,6 +657,21 @@ define(function (require, exports, module) {
});
});
+ describe('navigateAway', () => {
+ beforeEach(() => {
+ sinon.spy(notifier, 'trigger');
+ });
+
+ it('navigates to a page, propagates the clearQueryParams options', () => {
+ view.navigateAway('wibble');
+
+ assert.isTrue(notifier.trigger.calledWith('navigate', {
+ server: true,
+ url: 'wibble'
+ }));
+ });
+ });
+
describe('focus', () => {
beforeEach(() => {
$('#container').html(view.el);
@@ -2,116 +2,106 @@
* 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(function (require, exports, module) {
+define((require, exports, module) => {
'use strict';
+ const { assert } = require('chai');
const BaseView = require('views/base');
- const chai = require('chai');
const Cocktail = require('cocktail');
const Notifier = require('lib/channels/notifier');
const SignedOutNotificationMixin = require('views/mixins/signed-out-notification-mixin');
const sinon = require('sinon');
+ const WindowMock = require('../../../mocks/window');
- var assert = chai.assert;
-
- var View = BaseView.extend({});
+ const View = BaseView.extend({});
Cocktail.mixin(View, SignedOutNotificationMixin);
- describe('views/mixins/signed-out-notification-mixin', function () {
- it('exports correct interface', function () {
+ describe('views/mixins/signed-out-notification-mixin', () => {
+ it('exports correct interface', () => {
assert.lengthOf(Object.keys(SignedOutNotificationMixin), 3);
assert.isObject(SignedOutNotificationMixin.notifications);
assert.isFunction(SignedOutNotificationMixin.clearSessionAndNavigateToSignIn);
});
- describe('new View', function () {
- var notifier;
- var view;
+ describe('new View', () => {
+ let notifier;
+ let view;
+ let windowMock;
- beforeEach(function () {
+ beforeEach(() => {
notifier = new Notifier();
notifier.on = sinon.spy();
+ windowMock = new WindowMock();
view = new View({
- notifier: notifier
+ notifier: notifier,
+ window: windowMock
});
+ view.navigate = sinon.spy();
+ view.navigateAway = sinon.spy();
view.relier = {
- unset: sinon.spy()
+ get: sinon.spy()
};
view.user = {
clearSignedInAccountUid: sinon.spy()
};
- view._formPrefill = {
- clear: sinon.spy()
- };
- view.navigate = sinon.spy();
notifier.triggerAll = sinon.spy();
});
- afterEach(function () {
+ afterEach(() => {
view.destroy();
});
- it('calls notifier.on correctly', function () {
+ it('calls notifier.on correctly', () => {
assert.equal(notifier.on.callCount, 1);
var args = notifier.on.args[0];
assert.lengthOf(args, 2);
assert.equal(args[0], Notifier.SIGNED_OUT);
assert.isFunction(args[1]);
});
- describe('clearSessionAndNavigateToSignIn', function () {
- beforeEach(function () {
+ describe('clearSessionAndNavigateToSignIn', () => {
+ beforeEach(() => {
notifier.on.args[0][1]();
});
- it('calls relier.unset correctly', function () {
- assert.equal(view.relier.unset.callCount, 2);
-
- assert.lengthOf(view.relier.unset.args[0], 1);
- assert.equal(view.relier.unset.args[0][0], 'uid');
-
- assert.lengthOf(view.relier.unset.args[1], 1);
- assert.equal(view.relier.unset.args[1][0], 'email');
- });
-
- it('calls user.clearSignedInAccountUid correctly', function () {
+ it('calls user.clearSignedInAccountUid correctly', () => {
assert.equal(view.user.clearSignedInAccountUid.callCount, 1);
assert.lengthOf(view.user.clearSignedInAccountUid.args[0], 0);
});
- it('calls _formPrefill.clear correctly', function () {
- assert.equal(view._formPrefill.clear.callCount, 1);
- assert.lengthOf(view._formPrefill.clear.args[0], 0);
+ it('navigated correctly', () => {
+ assert.equal(view.navigateAway.callCount, 1);
+ assert.equal(view.navigateAway.args[0][0], '/signin');
});
- it('calls navigate correctly', function () {
- assert.equal(view.navigate.callCount, 1);
- assert.isTrue(view.navigate.calledAfter(view.user.clearSignedInAccountUid));
- assert.isTrue(view.navigate.calledAfter(view._formPrefill.clear));
- var args = view.navigate.args[0];
- assert.lengthOf(args, 3);
- assert.equal(args[0], 'signin');
- assert.isObject(args[1]);
- assert.lengthOf(Object.keys(args[1]), 1);
- assert.equal(args[1].success, 'Signed out successfully');
- assert.lengthOf(Object.keys(args[2]), 1);
- assert.isTrue(args[2].clearQueryParams);
- });
-
- it('does not call notifier.triggerAll', function () {
+ it('does not call notifier.triggerAll', () => {
assert.equal(notifier.triggerAll.callCount, 0);
});
});
- describe('delete _formPrefill', function () {
- beforeEach(function () {
- view._formPrefill = null;
+ describe('clearSessionAndNavigateToSignIn with relier data', () => {
+ beforeEach(() => {
+ view.relier.get = sinon.spy(key => `mock_${key}`);
+ notifier.on.args[0][1]();
+ });
+
+ it('calls user.clearSignedInAccountUid correctly', () => {
+ assert.equal(view.user.clearSignedInAccountUid.callCount, 1);
+ assert.lengthOf(view.user.clearSignedInAccountUid.args[0], 0);
});
- it('clearSessionAndNavigateToSignIn does not throw', function () {
- assert.doesNotThrow(function () {
- notifier.on.args[0][1]();
- });
+ it('navigated correctly', () => {
+ assert.equal(view.navigateAway.callCount, 1);
+ assert.equal(view.navigateAway.args[0][0], '/signin?' + [
+ 'context=mock_context',
+ 'entrypoint=mock_entrypoint',
+ 'service=mock_service',
+ 'utm_campaign=mock_utmCampaign',
+ 'utm_content=mock_utmContent',
+ 'utm_medium=mock_utmMedium',
+ 'utm_source=mock_utmSource',
+ 'utm_term=mock_utmTerm'
+ ].join('&'));
});
});
});
Oops, something went wrong.

0 comments on commit 3ea1a9a

Please sign in to comment.