diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-redirect.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-redirect.js index 41890498322..a2319e6cf43 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-redirect.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-redirect.js @@ -345,8 +345,10 @@ export default BaseAuthenticationBroker.extend({ this.clearOriginalTabMarker(); return this.getOAuthResult(account).then(result => { result = _.extend(result, additionalResultData); - result.declinedSyncEngines = account.get('declinedSyncEngines'); - result.offeredSyncEngines = account.get('offeredSyncEngines'); + if (account.get('declinedSyncEngines')) { + result.declinedSyncEngines = account.get('declinedSyncEngines'); + result.offeredSyncEngines = account.get('offeredSyncEngines'); + } return this.sendOAuthResultToRelier(result); }); diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-webchannel-v1.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-webchannel-v1.js index 445367bccfa..1dd19e2164c 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-webchannel-v1.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/oauth-webchannel-v1.js @@ -5,7 +5,6 @@ /** * WebChannel OAuth broker that speaks 'v1' of the protocol. */ - import _ from 'underscore'; import ChannelMixin from './mixins/channel'; import Cocktail from 'cocktail'; @@ -41,12 +40,9 @@ const OAuthWebChannelBroker = OAuthRedirectAuthenticationBroker.extend({ this._metrics = options.metrics; proto.initialize.call(this, options); - - this.request( - this.getCommand('FXA_STATUS', { - service: this.relier.get('service'), - }) - ).then(response => this.onFxaStatus(response)); + this.request(this.getCommand('FXA_STATUS'), { + service: this.relier.get('service'), + }).then(response => this.onFxaStatus(response)); }, /** @@ -94,16 +90,8 @@ const OAuthWebChannelBroker = OAuthRedirectAuthenticationBroker.extend({ DELAY_BROKER_RESPONSE_MS: 100, - sendOAuthResultToRelier(result, account) { + sendOAuthResultToRelier(result) { return this._metrics.flush().then(() => { - const extraParams = {}; - if (result.error) { - extraParams.error = result.error; - } - if (result.action) { - extraParams.action = result.action; - } - result.redirect = Constants.OAUTH_WEBCHANNEL_REDIRECT; return this.send(this.getCommand('OAUTH_LOGIN'), result); diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/mixins/supplicant.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/mixins/supplicant.js new file mode 100644 index 00000000000..1a6d4645cc5 --- /dev/null +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/mixins/supplicant.js @@ -0,0 +1,62 @@ +/* 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/. */ + +import OAuthErrors from '../../../../lib/oauth-errors'; +import PairingChannelClient from '../../../../lib/pairing-channel-client'; +import SupplicantStateMachine from '../../../pairing/supplicant-state-machine'; +import setRemoteMetaData from '../remote-metadata'; + +/** + * Shared functions of the supplicant auth brokers + */ + +const SupplicantMixin = { + init(options) { + const { config, notifier, relier } = options; + + if (!config.pairingClients.includes(relier.get('clientId'))) { + // only approved clients may pair + throw OAuthErrors.toError('INVALID_PAIRING_CLIENT'); + } + + const channelServerUri = config.pairingChannelServerUri; + const { channelId, channelKey } = relier.toJSON(); + if (channelId && channelKey && channelServerUri) { + this.pairingChannelClient = new PairingChannelClient( + { + channelId, + channelKey, + channelServerUri, + }, + { + importPairingChannel: options.importPairingChannel, + notifier, + } + ); + + this.suppStateMachine = new SupplicantStateMachine( + {}, + { + broker: this, + notifier, + pairingChannelClient: this.pairingChannelClient, + relier, + } + ); + + this.pairingChannelClient.open(); + } else { + throw new Error('Failed to initialize supplicant'); + } + }, + afterSupplicantApprove() { + return Promise.resolve().then(() => { + this.notifier.trigger('pair:supp:authorize'); + }); + }, + + setRemoteMetaData: setRemoteMetaData, +}; + +export default SupplicantMixin; diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant-webchannel.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant-webchannel.js index 66a41efb750..0420b24a409 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant-webchannel.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant-webchannel.js @@ -2,60 +2,19 @@ * 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/. */ -import OAuthErrors from '../../../lib/oauth-errors'; +import Cocktail from 'cocktail'; import OAuthWebChannelBroker from '../oauth-webchannel-v1'; -import PairingChannelClient from '../../../lib/pairing-channel-client'; -import setRemoteMetaData from './remote-metadata'; -import SupplicantStateMachine from '../../pairing/supplicant-state-machine'; -import Url from '../../../lib/url'; +import SupplicantMixin from './mixins/supplicant'; -export default class SupplicantWebChannelBroker extends OAuthWebChannelBroker { - type = 'supplicant'; +/** + * SupplicantWebChannelBroker extends OAuthWebChannelBroker to provide a WebChannel flow + */ +class SupplicantWebChannelBroker extends OAuthWebChannelBroker { + type = 'supplicant-webchannel'; initialize(options = {}) { super.initialize(options); - const { config, notifier, relier } = options; - - if (!config.pairingClients.includes(relier.get('clientId'))) { - // only approved clients may pair - throw OAuthErrors.toError('INVALID_PAIRING_CLIENT'); - } - - const channelServerUri = config.pairingChannelServerUri; - const { channelId, channelKey } = relier.toJSON(); - if (channelId && channelKey && channelServerUri) { - this.pairingChannelClient = new PairingChannelClient( - { - channelId, - channelKey, - channelServerUri, - }, - { - importPairingChannel: options.importPairingChannel, - notifier, - } - ); - - this.suppStateMachine = new SupplicantStateMachine( - {}, - { - broker: this, - notifier, - pairingChannelClient: this.pairingChannelClient, - relier, - } - ); - - this.pairingChannelClient.open(); - } else { - throw new Error('Failed to initialize supplicant'); - } - } - - afterSupplicantApprove() { - return Promise.resolve().then(() => { - this.notifier.trigger('pair:supp:authorize'); - }); + this.init(options); } sendCodeToRelier() { @@ -70,6 +29,8 @@ export default class SupplicantWebChannelBroker extends OAuthWebChannelBroker { this.sendOAuthResultToRelier(result); }); } - - setRemoteMetaData = setRemoteMetaData; } + +Cocktail.mixin(SupplicantWebChannelBroker, SupplicantMixin); + +export default SupplicantWebChannelBroker; diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant.js index 5ce00c1d220..d0cbc11c5a6 100644 --- a/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant.js +++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/pairing/supplicant.js @@ -2,61 +2,20 @@ * 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/. */ -import OAuthErrors from '../../../lib/oauth-errors'; +import Cocktail from 'cocktail'; import OAuthRedirectBroker from '../oauth-redirect'; -import PairingChannelClient from '../../../lib/pairing-channel-client'; -import setRemoteMetaData from './remote-metadata'; -import SupplicantStateMachine from '../../pairing/supplicant-state-machine'; +import SupplicantMixin from './mixins/supplicant'; import Url from '../../../lib/url'; -export default class SupplicantBroker extends OAuthRedirectBroker { +/** + * SupplicantBroker extends OAuthRedirectBroker to provide a redirect behaviour as an OAuth flow + */ +class SupplicantBroker extends OAuthRedirectBroker { type = 'supplicant'; initialize(options = {}) { super.initialize(options); - - const { config, notifier, relier } = options; - - if (!config.pairingClients.includes(relier.get('clientId'))) { - // only approved clients may pair - throw OAuthErrors.toError('INVALID_PAIRING_CLIENT'); - } - - const channelServerUri = config.pairingChannelServerUri; - const { channelId, channelKey } = relier.toJSON(); - if (channelId && channelKey && channelServerUri) { - this.pairingChannelClient = new PairingChannelClient( - { - channelId, - channelKey, - channelServerUri, - }, - { - importPairingChannel: options.importPairingChannel, - notifier, - } - ); - - this.suppStateMachine = new SupplicantStateMachine( - {}, - { - broker: this, - notifier, - pairingChannelClient: this.pairingChannelClient, - relier, - } - ); - - this.pairingChannelClient.open(); - } else { - throw new Error('Failed to initialize supplicant'); - } - } - - afterSupplicantApprove() { - return Promise.resolve().then(() => { - this.notifier.trigger('pair:supp:authorize'); - }); + this.init(options); } sendCodeToRelier() { @@ -72,6 +31,8 @@ export default class SupplicantBroker extends OAuthRedirectBroker { this.sendOAuthResultToRelier(result); }); } - - setRemoteMetaData = setRemoteMetaData; } + +Cocktail.mixin(SupplicantBroker, SupplicantMixin); + +export default SupplicantBroker; diff --git a/packages/fxa-content-server/app/tests/spec/lib/channels/web.js b/packages/fxa-content-server/app/tests/spec/lib/channels/web.js index c637d4c1b26..240f1b70723 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/channels/web.js +++ b/packages/fxa-content-server/app/tests/spec/lib/channels/web.js @@ -41,6 +41,7 @@ describe('lib/channels/web', () => { assert.ok(WebChannel.LOADED); assert.ok(WebChannel.LOGIN); assert.ok(WebChannel.LOGOUT); + assert.ok(WebChannel.OAUTH_LOGIN); assert.ok(WebChannel.PAIR_AUTHORIZE); assert.ok(WebChannel.PAIR_DECLINE); assert.ok(WebChannel.PAIR_REQUEST_SUPPLICANT_METADATA); @@ -54,7 +55,7 @@ describe('lib/channels/web', () => { window: windowMock, }); - assert.lengthOf(Object.keys(channel.COMMANDS), 16); + assert.lengthOf(Object.keys(channel.COMMANDS), 17); assert.ok(channel.COMMANDS.CAN_LINK_ACCOUNT); assert.ok(channel.COMMANDS.CHANGE_PASSWORD); assert.ok(channel.COMMANDS.DELETE); @@ -63,6 +64,7 @@ describe('lib/channels/web', () => { assert.ok(channel.COMMANDS.LOADED); assert.ok(channel.COMMANDS.LOGIN); assert.ok(channel.COMMANDS.LOGOUT); + assert.ok(channel.COMMANDS.OAUTH_LOGIN); assert.ok(channel.COMMANDS.PAIR_AUTHORIZE); assert.ok(channel.COMMANDS.PAIR_DECLINE); assert.ok(channel.COMMANDS.PAIR_COMPLETE); diff --git a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-redirect.js b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-redirect.js index 573fa3f9fe2..722fe615f9e 100644 --- a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-redirect.js +++ b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-redirect.js @@ -196,6 +196,28 @@ describe('models/auth_brokers/oauth-redirect', () => { ); }); }); + + it('handles declinedSyncEngines and offeredSyncEngines', () => { + account.set('declinedSyncEngines', ['history']); + account.set('offeredSyncEngines', ['history']); + + sinon.stub(broker, 'getOAuthResult').callsFake(() => { + return Promise.resolve({}); + }); + + sinon.stub(broker, 'sendOAuthResultToRelier').callsFake(() => { + return Promise.resolve(); + }); + + return broker + .persistVerificationData(account) + .then(() => { + return broker.finishOAuthFlow(account); + }) + .then(result => { + assert.equal(result, 'thing here!'); + }); + }); }); describe('afterResetPasswordConfirmationPoll', function() { diff --git a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-webchannel-v1.js b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-webchannel-v1.js index 05cec84d57f..0d5e0e55bc4 100644 --- a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-webchannel-v1.js +++ b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/oauth-webchannel-v1.js @@ -24,6 +24,7 @@ function generateOAuthCode() { return code; } +const OAUTH_STATUS_MESSAGE = 'fxaccounts:fxa_status'; const OAUTH_LOGIN_MESSAGE = 'fxaccounts:oauth_login'; const REDIRECT_URI = 'https://127.0.0.1:8080'; const VALID_OAUTH_CODE = generateOAuthCode(); @@ -59,12 +60,12 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { flush: sinon.spy(() => Promise.resolve()), logEvent: () => {}, }; - relier = new Relier(); - relier.set({ + relier = new Relier({ action: 'action', clientId: 'clientId', redirectUri: REDIRECT_URI, scope: 'scope', + service: 'service', state: 'state', }); user = new User(); @@ -74,6 +75,9 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { channelMock.send = sinon.spy(() => { return Promise.resolve(); }); + channelMock.request = sinon.spy(() => { + return Promise.resolve(); + }); account = user.initAccount({ sessionToken: 'abc123', @@ -91,7 +95,30 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { sinon.spy(broker, 'finishOAuthFlow'); }); - describe('with an error', () => { + it('status message', () => { + const statusMsg = channelMock.request.getCall(0).args; + assert.equal(statusMsg[0], OAUTH_STATUS_MESSAGE); + assert.deepEqual(statusMsg[1], { service: 'service' }); + }); + + it('passes code and state', () => { + return broker + .sendOAuthResultToRelier({ + code: 'code', + state: 'state', + }) + .then(() => { + const loginMsg = channelMock.send.getCall(0).args; + assert.equal(loginMsg[0], OAUTH_LOGIN_MESSAGE); + assert.deepEqual(loginMsg[1], { + code: 'code', + state: 'state', + redirect: Constants.OAUTH_WEBCHANNEL_REDIRECT, + }); + }); + }); + + describe('login with an error', () => { it('appends an error query parameter', () => { return broker .sendOAuthResultToRelier({ @@ -100,9 +127,9 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { .then(() => { assert.isTrue(metrics.flush.calledOnce); assert.lengthOf(metrics.flush.getCall(0).args, 0); - const message = channelMock.send.getCall(0).args; - assert.equal(message[0], OAUTH_LOGIN_MESSAGE); - assert.deepEqual(message[1], { + const loginMsg = channelMock.send.getCall(0).args; + assert.equal(loginMsg[0], OAUTH_LOGIN_MESSAGE); + assert.deepEqual(loginMsg[1], { error: 'error', redirect: Constants.OAUTH_WEBCHANNEL_REDIRECT, }); @@ -110,7 +137,7 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { }); }); - describe('with an action', () => { + describe('login with an action', () => { it('appends an action query parameter', () => { var action = Constants.OAUTH_ACTION_SIGNIN; return broker @@ -118,9 +145,9 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { action: action, }) .then(() => { - const message = channelMock.send.getCall(0).args; - assert.equal(message[0], OAUTH_LOGIN_MESSAGE); - assert.deepEqual(message[1], { + const loginMsg = channelMock.send.getCall(0).args; + assert.equal(loginMsg[0], OAUTH_LOGIN_MESSAGE); + assert.deepEqual(loginMsg[1], { action: action, redirect: Constants.OAUTH_WEBCHANNEL_REDIRECT, }); @@ -128,16 +155,16 @@ describe('models/auth_brokers/oauth-webchannel-v1', () => { }); }); - describe('with existing query parameters', () => { + describe('login with existing query parameters', () => { it('passes through existing parameters unchanged', () => { return broker .sendOAuthResultToRelier({ error: 'error', }) .then(() => { - const message = channelMock.send.getCall(0).args; - assert.equal(message[0], OAUTH_LOGIN_MESSAGE); - assert.deepEqual(message[1], { + const loginMsg = channelMock.send.getCall(0).args; + assert.equal(loginMsg[0], OAUTH_LOGIN_MESSAGE); + assert.deepEqual(loginMsg[1], { error: 'error', redirect: Constants.OAUTH_WEBCHANNEL_REDIRECT, });