зеркало из https://github.com/mozilla/fxa.git
Merge pull request #17816 from mozilla/FXA-10528
refactor(settings): Create OAuthNative integration, rename OAuth integration to OAuthWeb
This commit is contained in:
Коммит
ad7a75dc8c
|
@ -22,9 +22,9 @@ export type SyncEngineId = EngineConfig['id'] | WebChannelEngineConfig['id'];
|
|||
/* These sync engines are always offered to the user in Sync fx_desktop_v3
|
||||
* and other engines can be received and added with a webchannel message.
|
||||
*
|
||||
* For OAuth Sync (oauth_webchannel_v1) which includes sync mobile and sync
|
||||
* desktop on FF 123+, we do not display options by default and instead, we
|
||||
* receive the webchannel message and overwrite the options.
|
||||
* For OAuth Sync (oauth_webchannel_v1) which includes sync mobile and
|
||||
* oauth sync desktop, we do not display options by default and instead,
|
||||
* we receive the webchannel message and overwrite the options.
|
||||
*/
|
||||
export const defaultDesktopV3SyncEngineConfigs = [
|
||||
{
|
||||
|
|
|
@ -57,7 +57,7 @@ export class DefaultIntegrationFlags implements IntegrationFlags {
|
|||
return this.searchParam('context') === Constants.FX_DESKTOP_V3_CONTEXT;
|
||||
}
|
||||
|
||||
// Sync mobile, Sync FF Desktop 123+, and supplicant pairing use this context
|
||||
// Sync mobile, Sync FF OAuth Desktop, and supplicant pairing use this context
|
||||
isOAuthWebChannelContext() {
|
||||
return this.searchParam('context') === Constants.OAUTH_WEBCHANNEL_CONTEXT;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import {
|
|||
Integration,
|
||||
IntegrationType,
|
||||
OAuthIntegration,
|
||||
OAuthNativeClients,
|
||||
OAuthNativeIntegration,
|
||||
OAuthWebIntegration,
|
||||
PairingAuthorityIntegration,
|
||||
PairingSupplicantIntegration,
|
||||
RelierClientInfo,
|
||||
|
@ -25,6 +28,7 @@ type IntegrationFlagOverrides = {
|
|||
isOAuth?: boolean;
|
||||
isServiceSync?: boolean;
|
||||
isV3DesktopContext?: boolean;
|
||||
isOAuthWebChannelContext?: boolean;
|
||||
};
|
||||
|
||||
type FactoryCallCounts = {
|
||||
|
@ -67,13 +71,14 @@ describe('lib/integrations/integration-factory', () => {
|
|||
sandbox
|
||||
.stub(flags, 'isV3DesktopContext')
|
||||
.returns(!!flagOverrides.isV3DesktopContext);
|
||||
sandbox
|
||||
.stub(flags, 'isOAuthWebChannelContext')
|
||||
.returns(!!flagOverrides.isOAuthWebChannelContext);
|
||||
|
||||
urlQueryData.set('scope', 'profile');
|
||||
urlQueryData.set('client_id', '720bc80adfa6988d');
|
||||
urlQueryData.set('redirect_uri', 'https://redirect.to');
|
||||
|
||||
urlHashData.set('scope', 'profile');
|
||||
|
||||
// Create a factory with current state
|
||||
const factory = new IntegrationFactory({
|
||||
window,
|
||||
|
@ -149,21 +154,9 @@ describe('lib/integrations/integration-factory', () => {
|
|||
expect(integration.wantsKeys()).toBeFalsy();
|
||||
expect(integration.isTrusted()).toBeTruthy();
|
||||
});
|
||||
|
||||
// TODO: Remove with approval.
|
||||
//
|
||||
// I think maybe this is feature envy, perhaps we should have some dedicated thing that checks integration state
|
||||
// and account state to determine if features are needed. As far as I can tell the integration models
|
||||
// themselves really shouldn't know or care about 'accounts'
|
||||
//
|
||||
// describe('accountNeedsPermissions', function () {
|
||||
// it('returns `false`', function () {
|
||||
// assert.isFalse(integration.accountNeedsPermissions());
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
describe('SyncDesktop creation', () => {
|
||||
describe('SyncDesktopV3 creation', () => {
|
||||
const ACTION = 'email';
|
||||
const CONTEXT = 'fx_desktop_v3';
|
||||
const COUNTRY = 'RO';
|
||||
|
@ -206,7 +199,7 @@ describe('lib/integrations/integration-factory', () => {
|
|||
// TODO: Port remaining tests from content-server
|
||||
});
|
||||
|
||||
describe('OAuthIntegration creation', () => {
|
||||
describe('OAuthWebIntegration creation', () => {
|
||||
let integration: OAuthIntegration;
|
||||
|
||||
describe('OAuth redirect', () => {
|
||||
|
@ -214,40 +207,66 @@ describe('lib/integrations/integration-factory', () => {
|
|||
integration = await setup<OAuthIntegration>(
|
||||
{ isOAuth: true },
|
||||
{ initIntegration: 1, initOAuthIntegration: 1, initClientInfo: 1 },
|
||||
(i: Integration) => i instanceof OAuthIntegration
|
||||
(i: Integration) => i instanceof OAuthWebIntegration
|
||||
);
|
||||
});
|
||||
|
||||
it('has correct state', async () => {
|
||||
expect(integration.type).toEqual(IntegrationType.OAuth);
|
||||
expect(integration.type).toEqual(IntegrationType.OAuthWeb);
|
||||
expect(integration.isSync()).toBeFalsy();
|
||||
expect(integration.wantsKeys()).toBeFalsy();
|
||||
expect(integration.isTrusted()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OAuth Sync', () => {
|
||||
// TODO: Port remaining tests from content-server
|
||||
});
|
||||
|
||||
describe('OAuthNativeIntegration creation', () => {
|
||||
let integration: OAuthNativeIntegration;
|
||||
|
||||
describe('without sync', () => {
|
||||
beforeEach(async () => {
|
||||
integration = await setup<OAuthIntegration>(
|
||||
integration = await setup<OAuthNativeIntegration>(
|
||||
{ isOAuth: true },
|
||||
{ initIntegration: 1, initOAuthIntegration: 1, initClientInfo: 1 },
|
||||
(i: Integration) => i instanceof OAuthIntegration
|
||||
(i: Integration) => i instanceof OAuthNativeIntegration
|
||||
);
|
||||
});
|
||||
|
||||
it('has correct state', async () => {
|
||||
expect(integration.type).toEqual(IntegrationType.OAuthWeb);
|
||||
expect(integration.isSync()).toBeFalsy();
|
||||
expect(integration.wantsKeys()).toBeFalsy();
|
||||
expect(integration.isTrusted()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sync', () => {
|
||||
beforeEach(async () => {
|
||||
integration = await setup<OAuthNativeIntegration>(
|
||||
{ isOAuth: true, isOAuthWebChannelContext: true },
|
||||
{ initIntegration: 1, initOAuthIntegration: 1, initClientInfo: 1 },
|
||||
(i: Integration) => i instanceof OAuthNativeIntegration
|
||||
);
|
||||
await mockSearchParams({
|
||||
scope: Constants.OAUTH_OLDSYNC_SCOPE,
|
||||
context: Constants.OAUTH_WEBCHANNEL_CONTEXT,
|
||||
clientId: OAuthNativeClients.FirefoxIOS,
|
||||
});
|
||||
sandbox.stub(integration, 'clientInfo').get(() => ({
|
||||
...clientInfo,
|
||||
clientId: OAuthNativeClients.FirefoxIOS,
|
||||
}));
|
||||
});
|
||||
|
||||
it('has correct state', async () => {
|
||||
expect(integration.type).toEqual(IntegrationType.OAuth);
|
||||
expect(integration.type).toEqual(IntegrationType.OAuthNative);
|
||||
expect(integration.isSync()).toBeTruthy();
|
||||
expect(integration.wantsKeys()).toBeTruthy();
|
||||
expect(integration.isTrusted()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Port remaining tests from content-server
|
||||
});
|
||||
|
||||
describe('PairingSupplicantIntegration creation', () => {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import {
|
||||
OAuthIntegration,
|
||||
OAuthWebIntegration,
|
||||
OAuthNativeIntegration,
|
||||
PairingAuthorityIntegration,
|
||||
PairingSupplicantIntegration,
|
||||
Integration,
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
WebIntegration,
|
||||
RelierClientInfo,
|
||||
RelierSubscriptionInfo,
|
||||
OAuthIntegration,
|
||||
} from '../../models/integrations';
|
||||
import {
|
||||
ModelDataStore,
|
||||
|
@ -106,7 +108,11 @@ export class IntegrationFactory {
|
|||
} else if (flags.isDevicePairingAsSupplicant()) {
|
||||
return this.createPairingSupplicationIntegration(data, storageData);
|
||||
} else if (flags.isOAuth()) {
|
||||
return this.createOAuthIntegration(data, storageData);
|
||||
if (flags.isOAuthWebChannelContext()) {
|
||||
return this.createOAuthNativeIntegration(data, storageData);
|
||||
} else {
|
||||
return this.createOAuthWebIntegration(data, storageData);
|
||||
}
|
||||
} else if (flags.isV3DesktopContext()) {
|
||||
return this.createSyncDesktopV3Integration(data);
|
||||
} else if (flags.isServiceSync()) {
|
||||
|
@ -144,12 +150,31 @@ export class IntegrationFactory {
|
|||
return integration;
|
||||
}
|
||||
|
||||
private createOAuthIntegration(
|
||||
private createOAuthWebIntegration(
|
||||
data: ModelDataStore,
|
||||
storageData: ModelDataStore
|
||||
) {
|
||||
// Resolve configuration settings for oauth relier
|
||||
const integration = new OAuthIntegration(data, storageData, config.oauth);
|
||||
const integration = new OAuthWebIntegration(
|
||||
data,
|
||||
storageData,
|
||||
config.oauth
|
||||
);
|
||||
this.initIntegration(integration);
|
||||
this.initOAuthIntegration(integration, this.flags);
|
||||
this.initClientInfo(integration);
|
||||
return integration;
|
||||
}
|
||||
|
||||
private createOAuthNativeIntegration(
|
||||
data: ModelDataStore,
|
||||
storageData: ModelDataStore
|
||||
) {
|
||||
const integration = new OAuthNativeIntegration(
|
||||
data,
|
||||
storageData,
|
||||
config.oauth
|
||||
);
|
||||
this.initIntegration(integration);
|
||||
this.initOAuthIntegration(integration, this.flags);
|
||||
this.initClientInfo(integration);
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface IntegrationFlags {
|
|||
isDevicePairingAsAuthority(): boolean;
|
||||
isDevicePairingAsSupplicant(): boolean;
|
||||
isOAuth(): boolean;
|
||||
isOAuthWebChannelContext(): boolean;
|
||||
isV3DesktopContext(): boolean;
|
||||
isOAuthSuccessFlow(): { status: boolean; clientId: string };
|
||||
isOAuthVerificationFlow(): boolean;
|
||||
|
|
|
@ -7,8 +7,8 @@ import { useCallback } from 'react';
|
|||
import {
|
||||
Integration,
|
||||
OAuthIntegration,
|
||||
isOAuthNativeIntegrationSync,
|
||||
isOAuthIntegration,
|
||||
isSyncOAuthIntegration,
|
||||
} from '../../models';
|
||||
import { createEncryptedBundle } from '../crypto/scoped-keys';
|
||||
import { Constants } from '../constants';
|
||||
|
@ -181,7 +181,7 @@ export function useFinishOAuthFlowHandler(
|
|||
authClient: AuthClient,
|
||||
integration: Integration
|
||||
): UseFinishOAuthFlowHandlerResult {
|
||||
const isSyncOAuth = isSyncOAuthIntegration(integration);
|
||||
const isSyncOAuth = isOAuthNativeIntegrationSync(integration);
|
||||
|
||||
const finishOAuthFlowHandler: FinishOAuthFlowHandler = useCallback(
|
||||
async (accountUid, sessionToken, keyFetchToken, unwrapBKey) => {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
import { MozServices } from '../../lib/types';
|
||||
|
||||
export enum IntegrationType {
|
||||
OAuth = 'OAuth',
|
||||
OAuthWeb = 'OAuthWeb', // OAuth for non-browser services/RPs
|
||||
OAuthNative = 'OAuthNative', // OAuth for desktop & mobile clients
|
||||
PairingAuthority = 'PairingAuthority', // TODO
|
||||
PairingSupplicant = 'PairingSupplicant', // TODO
|
||||
SyncBasic = 'SyncBasic',
|
||||
|
@ -96,7 +97,19 @@ export abstract class Integration<
|
|||
this.features = { ...this.features, ...features } as T;
|
||||
}
|
||||
|
||||
isSync(): boolean {
|
||||
isSync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isDesktopSync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isFirefoxMobileClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isFirefoxDesktopClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -162,11 +175,6 @@ export abstract class Integration<
|
|||
isTrusted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: This seems like feature envy... Move logic elsewhere.
|
||||
// accountNeedsPermissions(account:RelierAccount): boolean {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
export class BaseIntegration<
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
export * from './base-integration';
|
||||
export * from './channel-info';
|
||||
export * from './client-info';
|
||||
export * from './oauth-integration';
|
||||
export * from './oauth-web-integration';
|
||||
export * from './oauth-native-integration';
|
||||
export * from './pairing-authority-integration';
|
||||
export * from './pairing-supplicant-integration';
|
||||
export * from './signin-signup-info';
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/* 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 { ModelDataStore, GenericData } from '../../lib/model-data';
|
||||
import {
|
||||
OAuthNativeClients,
|
||||
OAuthNativeIntegration,
|
||||
} from './oauth-native-integration';
|
||||
import { OAuthWebIntegration } from './oauth-web-integration';
|
||||
|
||||
function mockClientInfo(clientId: string) {
|
||||
return {
|
||||
clientId,
|
||||
serviceName: 'Firefox Sync',
|
||||
redirectUri: 'https://mock.com',
|
||||
trusted: true,
|
||||
imageUri: '',
|
||||
};
|
||||
}
|
||||
|
||||
describe('OAuthNativeIntegration', function () {
|
||||
let data: ModelDataStore;
|
||||
let oauthData: ModelDataStore;
|
||||
let model: OAuthNativeIntegration;
|
||||
|
||||
beforeEach(function () {
|
||||
data = new GenericData({
|
||||
clientId: OAuthNativeClients.FirefoxIOS,
|
||||
service: 'sync',
|
||||
});
|
||||
oauthData = new GenericData({
|
||||
scope: 'profile',
|
||||
});
|
||||
model = new OAuthNativeIntegration(data, oauthData, {
|
||||
scopedKeysEnabled: true,
|
||||
scopedKeysValidation: {},
|
||||
isPromptNoneEnabled: true,
|
||||
isPromptNoneEnabledClientIds: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('exists and extends OAuthWebIntegration', () => {
|
||||
expect(model).toBeDefined();
|
||||
expect(model instanceof OAuthWebIntegration).toBe(true);
|
||||
});
|
||||
|
||||
describe('isSync', () => {
|
||||
it('returns true for Firefox desktop client when service is sync', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
model.data.modelData.set('service', 'sync');
|
||||
expect(model.isSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Firefox desktop client when service is not defined', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
expect(model.isSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Firefox iOS client', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxIOS);
|
||||
expect(model.isSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Fenix client', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.Fenix);
|
||||
expect(model.isSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Fennec client', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.Fennec);
|
||||
expect(model.isSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for non-Sync services', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
model.data.modelData.set('service', 'relay');
|
||||
expect(model.isSync()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDesktopSync', () => {
|
||||
it('returns true when client is Firefox desktop and service is sync', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
model.data.modelData.set('service', 'sync');
|
||||
expect(model.isDesktopSync()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for non-sync service', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
model.data.modelData.set('service', 'relay');
|
||||
expect(model.isDesktopSync()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFirefoxMobileClient', () => {
|
||||
it('returns true for Firefox iOS client ID', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxIOS);
|
||||
expect(model.isFirefoxMobileClient()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Fenix client ID', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.Fenix);
|
||||
expect(model.isFirefoxMobileClient()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for Fennec client ID', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.Fennec);
|
||||
expect(model.isFirefoxMobileClient()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for unknown client ID', () => {
|
||||
model.clientInfo = mockClientInfo('unknown-client-id');
|
||||
expect(model.isFirefoxMobileClient()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFirefoxDesktopClient', () => {
|
||||
it('returns true for Firefox desktop client ID', () => {
|
||||
model.clientInfo = mockClientInfo(OAuthNativeClients.FirefoxDesktop);
|
||||
expect(model.isFirefoxDesktopClient()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for other client IDs', () => {
|
||||
expect(model.isFirefoxDesktopClient()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wantsKeys', () => {
|
||||
it('returns true', () => {
|
||||
expect(model.wantsKeys()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serviceName', () => {
|
||||
it('returns "Firefox" for non-sync services', () => {
|
||||
model.data.modelData.set('service', 'non-sync-service');
|
||||
expect(model.serviceName).toBe('Firefox');
|
||||
});
|
||||
|
||||
it('returns Sync service name for sync service', () => {
|
||||
model.data.modelData.set('service', 'sync');
|
||||
expect(model.serviceName).toBe('Firefox Sync');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,101 @@
|
|||
/* 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 { Constants } from '../../lib/constants';
|
||||
import { ModelDataStore } from '../../lib/model-data';
|
||||
import { Integration, IntegrationType } from './base-integration';
|
||||
import {
|
||||
OAuthIntegrationOptions,
|
||||
OAuthWebIntegration,
|
||||
} from './oauth-web-integration';
|
||||
|
||||
export function isOAuthNativeIntegration(integration: {
|
||||
type: IntegrationType;
|
||||
}): integration is OAuthNativeIntegration {
|
||||
return (
|
||||
(integration as OAuthNativeIntegration).type === IntegrationType.OAuthNative
|
||||
);
|
||||
}
|
||||
|
||||
export type OAuthIntegration = OAuthWebIntegration | OAuthNativeIntegration;
|
||||
|
||||
export function isOAuthIntegration(integration: {
|
||||
type: IntegrationType;
|
||||
}): integration is OAuthIntegration {
|
||||
return (
|
||||
(integration as OAuthWebIntegration).type === IntegrationType.OAuthWeb ||
|
||||
(integration as OAuthNativeIntegration).type === IntegrationType.OAuthNative
|
||||
);
|
||||
}
|
||||
|
||||
export enum OAuthNativeClients {
|
||||
FirefoxIOS = '1b1a3e44c54fbb58',
|
||||
FirefoxDesktop = '5882386c6d801776',
|
||||
Fenix = 'a2270f727f45f648',
|
||||
Fennec = '3332a18d142636cb',
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience function for the OAuthNativeIntegration type guard + isSync().
|
||||
*/
|
||||
export const isOAuthNativeIntegrationSync = (
|
||||
integration: Pick<Integration, 'type'>
|
||||
) => isOAuthNativeIntegration(integration) && integration.isSync();
|
||||
|
||||
/**
|
||||
* This integration is used for OAuth implementations by the browser including
|
||||
* mobile clients (currently all Sync), the oauth desktop sync flow, and the oauth
|
||||
* desktop flow for other services.
|
||||
*
|
||||
* FxA sends and receives web channel messages if this integration is created.
|
||||
*/
|
||||
export class OAuthNativeIntegration extends OAuthWebIntegration {
|
||||
constructor(
|
||||
data: ModelDataStore,
|
||||
protected readonly storageData: ModelDataStore,
|
||||
public readonly opts: OAuthIntegrationOptions
|
||||
) {
|
||||
super(data, storageData, opts, IntegrationType.OAuthNative);
|
||||
}
|
||||
|
||||
isSync() {
|
||||
// For now, all mobile clients are Sync. This may change in the future,
|
||||
// in which case we'll want a similar check to `isSyncDesktop`.
|
||||
return this.isDesktopSync() || this.isFirefoxMobileClient();
|
||||
}
|
||||
|
||||
isDesktopSync() {
|
||||
return (
|
||||
this.isFirefoxDesktopClient() &&
|
||||
// Sync oauth desktop should always provide a `service=sync` parameter but
|
||||
// we'll also default to Sync if it's missing.
|
||||
(this.data.service === undefined || this.data.service === 'sync')
|
||||
);
|
||||
}
|
||||
|
||||
isFirefoxMobileClient() {
|
||||
return (
|
||||
this.clientInfo?.clientId === OAuthNativeClients.FirefoxIOS ||
|
||||
this.clientInfo?.clientId === OAuthNativeClients.Fenix ||
|
||||
this.clientInfo?.clientId === OAuthNativeClients.Fennec
|
||||
);
|
||||
}
|
||||
|
||||
isFirefoxDesktopClient() {
|
||||
return this.clientInfo?.clientId === OAuthNativeClients.FirefoxDesktop;
|
||||
}
|
||||
|
||||
wantsKeys() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO in FXA-10313, check for "Relay" or whatever makes sense at implementation
|
||||
get serviceName() {
|
||||
if (this.data.service === 'sync') {
|
||||
return Constants.RELIER_SYNC_SERVICE_NAME;
|
||||
} else {
|
||||
return 'Firefox';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,15 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { ModelDataStore, GenericData } from '../../lib/model-data';
|
||||
import { OAuthIntegration, replaceItemInArray } from './oauth-integration';
|
||||
import {
|
||||
OAuthWebIntegration,
|
||||
replaceItemInArray,
|
||||
} from './oauth-web-integration';
|
||||
|
||||
describe('models/integrations/oauth-relier', function () {
|
||||
let data: ModelDataStore;
|
||||
let oauthData: ModelDataStore;
|
||||
let model: OAuthIntegration;
|
||||
let model: OAuthWebIntegration;
|
||||
|
||||
beforeEach(function () {
|
||||
data = new GenericData({
|
||||
|
@ -17,7 +20,7 @@ describe('models/integrations/oauth-relier', function () {
|
|||
oauthData = new GenericData({
|
||||
scope: 'profile',
|
||||
});
|
||||
model = new OAuthIntegration(data, oauthData, {
|
||||
model = new OAuthWebIntegration(data, oauthData, {
|
||||
scopedKeysEnabled: true,
|
||||
scopedKeysValidation: {},
|
||||
isPromptNoneEnabled: true,
|
||||
|
@ -39,7 +42,7 @@ describe('models/integrations/oauth-relier', function () {
|
|||
const SCOPE_WITH_OPENID = 'profile:email profile:uid openid';
|
||||
|
||||
function getIntegrationWithScope(scope: string) {
|
||||
const integration = new OAuthIntegration(
|
||||
const integration = new OAuthWebIntegration(
|
||||
new GenericData({
|
||||
scope,
|
||||
}),
|
||||
|
@ -170,6 +173,13 @@ describe('models/integrations/oauth-relier', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getService', () => {
|
||||
it('returns clientId as service', () => {
|
||||
model.data.modelData.set('client_id', '123');
|
||||
expect(model.getService()).toBe('123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceItemInArray', () => {
|
||||
it('handles empty array', () => {
|
||||
expect(replaceItemInArray([], 'foo', ['bar'])).toEqual([]);
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
import {
|
||||
BaseIntegration,
|
||||
Integration,
|
||||
IntegrationFeatures,
|
||||
IntegrationType,
|
||||
RelierAccount,
|
||||
RelierClientInfo,
|
||||
|
@ -29,10 +27,6 @@ import {
|
|||
} from 'class-validator';
|
||||
import { AuthUiError } from '../../lib/auth-errors/auth-errors';
|
||||
|
||||
export interface OAuthIntegrationFeatures extends IntegrationFeatures {
|
||||
webChannelSupport: boolean;
|
||||
}
|
||||
|
||||
export enum OAuthPrompt {
|
||||
CONSENT = 'consent',
|
||||
NONE = 'none',
|
||||
|
@ -40,25 +34,13 @@ export enum OAuthPrompt {
|
|||
}
|
||||
|
||||
type OAuthIntegrationTypes =
|
||||
| IntegrationType.OAuth
|
||||
| IntegrationType.OAuthWeb
|
||||
| IntegrationType.OAuthNative
|
||||
| IntegrationType.PairingSupplicant
|
||||
| IntegrationType.PairingAuthority;
|
||||
|
||||
export type SearchParam = IntegrationFlags['searchParam'];
|
||||
|
||||
export function isOAuthIntegration(integration: {
|
||||
type: IntegrationType;
|
||||
}): integration is OAuthIntegration {
|
||||
return (integration as OAuthIntegration).type === IntegrationType.OAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync mobile or sync desktop with context=oauth_webchannel_v1 (FF 123+)
|
||||
*/
|
||||
export const isSyncOAuthIntegration = (
|
||||
integration: Pick<Integration, 'type'>
|
||||
) => isOAuthIntegration(integration) && integration.isSync();
|
||||
|
||||
// TODO: probably move this somewhere else
|
||||
export class OAuthIntegrationData extends BaseIntegrationData {
|
||||
// TODO - Validation - Can we get a set of known client ids from config or api call? See https://github.com/mozilla/fxa/pull/15677#discussion_r1291534277
|
||||
|
@ -180,12 +162,18 @@ export type OAuthIntegrationOptions = {
|
|||
isPromptNoneEnabledClientIds: Array<string>;
|
||||
};
|
||||
|
||||
export class OAuthIntegration extends BaseIntegration<OAuthIntegrationFeatures> {
|
||||
/**
|
||||
* This integration is used for relying party OAuth implementations. FxA should
|
||||
* not send or receive web channel messages if this integration is created.
|
||||
*
|
||||
* This is a base class for OAuthNativeIntegration.
|
||||
*/
|
||||
export class OAuthWebIntegration extends BaseIntegration {
|
||||
constructor(
|
||||
data: ModelDataStore,
|
||||
protected readonly storageData: ModelDataStore,
|
||||
public readonly opts: OAuthIntegrationOptions,
|
||||
type: OAuthIntegrationTypes = IntegrationType.OAuth
|
||||
type: OAuthIntegrationTypes = IntegrationType.OAuthWeb
|
||||
) {
|
||||
super(type, new OAuthIntegrationData(data));
|
||||
this.setFeatures({
|
||||
|
@ -233,9 +221,8 @@ export class OAuthIntegration extends BaseIntegration<OAuthIntegrationFeatures>
|
|||
return this.getRedirectToRPUrl({ error: err.errno });
|
||||
}
|
||||
|
||||
// prefer client id if available (for oauth) otherwise fallback to service (e.g. for sync)
|
||||
getService() {
|
||||
return this.data.clientId || this.data.service;
|
||||
return this.data.clientId;
|
||||
}
|
||||
|
||||
restoreOAuthState() {
|
||||
|
@ -293,10 +280,6 @@ export class OAuthIntegration extends BaseIntegration<OAuthIntegrationFeatures>
|
|||
return this.clientInfo;
|
||||
}
|
||||
|
||||
isSync() {
|
||||
return this.data.context === Constants.OAUTH_WEBCHANNEL_CONTEXT;
|
||||
}
|
||||
|
||||
isTrusted() {
|
||||
return this.clientInfo?.trusted === true;
|
||||
}
|
||||
|
@ -323,9 +306,6 @@ export class OAuthIntegration extends BaseIntegration<OAuthIntegrationFeatures>
|
|||
}
|
||||
|
||||
wantsKeys(): boolean {
|
||||
if (this.isSync()) {
|
||||
return true;
|
||||
}
|
||||
if (!this.opts.scopedKeysEnabled) {
|
||||
return false;
|
||||
}
|
|
@ -5,10 +5,10 @@
|
|||
import { ModelDataStore } from '../../lib/model-data';
|
||||
import { IntegrationType } from './base-integration';
|
||||
import {
|
||||
OAuthIntegration,
|
||||
OAuthWebIntegration,
|
||||
OAuthIntegrationData,
|
||||
OAuthIntegrationOptions,
|
||||
} from './oauth-integration';
|
||||
} from './oauth-web-integration';
|
||||
import { bind, KeyTransforms as T } from '../../lib/model-data';
|
||||
import { IsBase64, IsNotEmpty } from 'class-validator';
|
||||
|
||||
|
@ -24,7 +24,7 @@ export class PairingAuthorityIntegrationData extends OAuthIntegrationData {
|
|||
//
|
||||
// Also keep in mind, in content-server:
|
||||
// Authority auth_broker extends from Base auth_broker and Authority relier extends from OAuthRelier
|
||||
export class PairingAuthorityIntegration extends OAuthIntegration {
|
||||
export class PairingAuthorityIntegration extends OAuthWebIntegration {
|
||||
constructor(
|
||||
data: ModelDataStore,
|
||||
protected readonly storageData: ModelDataStore,
|
||||
|
|
|
@ -5,7 +5,10 @@ import { ModelDataStore } from '../../lib/model-data';
|
|||
import { OAuthIntegrationData } from '.';
|
||||
import { IntegrationType } from './base-integration';
|
||||
import { bind } from '../../lib/model-data';
|
||||
import { OAuthIntegration, OAuthIntegrationOptions } from './oauth-integration';
|
||||
import {
|
||||
OAuthWebIntegration,
|
||||
OAuthIntegrationOptions,
|
||||
} from './oauth-web-integration';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
// TODO in the 'Pairing' React epic. This shouldn't have any `feature` overrides but feel
|
||||
|
@ -19,7 +22,7 @@ export class PairingSupplicantIntegrationData extends OAuthIntegrationData {
|
|||
scope: string | undefined = '';
|
||||
}
|
||||
|
||||
export class PairingSupplicantIntegration extends OAuthIntegration {
|
||||
export class PairingSupplicantIntegration extends OAuthWebIntegration {
|
||||
constructor(
|
||||
data: ModelDataStore,
|
||||
protected readonly storageData: ModelDataStore,
|
||||
|
|
|
@ -70,6 +70,9 @@ type SyncIntegrationTypes =
|
|||
* via webchannels. Currently it is only used 1) when a user is on a verification page
|
||||
* through Sync in a different browser, which will no longer be the case once we use
|
||||
* codes for reset PW, and 2) as a base class for sync desktop v3.
|
||||
*
|
||||
* TODO in FXA-10313, let's just get rid of this now that we're on codes.
|
||||
* Move methods into SyncDesktopV3Integration.
|
||||
*/
|
||||
export class SyncBasicIntegration<
|
||||
T extends SyncIntegrationFeatures
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from './sync-basic-integration';
|
||||
|
||||
/**
|
||||
* Sync desktop with context=fx_desktop_v3 (FF < 123)
|
||||
* Sync desktop with context=fx_desktop_v3
|
||||
*/
|
||||
export function isSyncDesktopV3Integration(integration: {
|
||||
type: IntegrationType;
|
||||
|
@ -21,12 +21,22 @@ export function isSyncDesktopV3Integration(integration: {
|
|||
);
|
||||
}
|
||||
|
||||
/* This is a legacy integration for desktop Firefox < 123 that must be supported
|
||||
/* This is a legacy integration for desktop Firefox that must be supported
|
||||
* for the foreseeable future.
|
||||
*
|
||||
* FxA sends and receives web channel messages if this integration is created.
|
||||
*/
|
||||
export class SyncDesktopV3Integration extends SyncBasicIntegration<SyncIntegrationFeatures> {
|
||||
constructor(data: ModelDataStore) {
|
||||
super(data, IntegrationType.SyncDesktopV3);
|
||||
this.setFeatures({ allowUidChange: true });
|
||||
}
|
||||
|
||||
isDesktopSync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isFirefoxDesktopClient() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ import InputText from '../../components/InputText';
|
|||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import ThirdPartyAuth from '../../components/ThirdPartyAuth';
|
||||
import TermsPrivacyAgreement from '../../components/TermsPrivacyAgreement';
|
||||
import { isOAuthIntegration } from '../../models';
|
||||
import {
|
||||
isClientMonitor,
|
||||
isClientPocket,
|
||||
} from '../../models/integrations/client-matching';
|
||||
import { isOAuthIntegration } from '../../models';
|
||||
|
||||
export const Index = ({
|
||||
integration,
|
||||
|
|
|
@ -5,11 +5,28 @@
|
|||
import React from 'react';
|
||||
import { LocationProvider } from '@reach/router';
|
||||
import { MozServices } from '../../lib/types';
|
||||
import { IntegrationType } from '../../models';
|
||||
import { IntegrationType, OAuthIntegration } from '../../models';
|
||||
import { IndexIntegration } from './interfaces';
|
||||
import Index from '.';
|
||||
import { MOCK_CLIENT_ID } from '../mocks';
|
||||
|
||||
export function createMockIndexOAuthIntegration({
|
||||
clientId = MOCK_CLIENT_ID,
|
||||
}): IndexIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuthWeb,
|
||||
isSync: () => false,
|
||||
getService: () => clientId,
|
||||
};
|
||||
}
|
||||
export function createMockIndexSyncIntegration(): IndexIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuthNative,
|
||||
isSync: () => true,
|
||||
getService: () => MOCK_CLIENT_ID,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockIndexWebIntegration(): IndexIntegration {
|
||||
return {
|
||||
type: IntegrationType.Web,
|
||||
|
@ -18,24 +35,6 @@ export function createMockIndexWebIntegration(): IndexIntegration {
|
|||
};
|
||||
}
|
||||
|
||||
export function createMockIndexSyncIntegration(): IndexIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuth,
|
||||
isSync: () => true,
|
||||
getService: () => MOCK_CLIENT_ID,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockIndexOAuthIntegration({
|
||||
clientId = MOCK_CLIENT_ID,
|
||||
}): IndexIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuth,
|
||||
isSync: () => false,
|
||||
getService: () => clientId,
|
||||
};
|
||||
}
|
||||
|
||||
export const Subject = ({
|
||||
integration = createMockIndexWebIntegration(),
|
||||
serviceName = MozServices.Default,
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
import { Integration, IntegrationType } from '../../../models';
|
||||
|
||||
export const mockResetPasswordOAuthIntegration = () => {
|
||||
export const mockResetPasswordOAuthNativeIntegration = () => {
|
||||
const mockIntegration = {
|
||||
type: IntegrationType.OAuth,
|
||||
type: IntegrationType.OAuthNative,
|
||||
getService: () => 'sync',
|
||||
isSync: () => true,
|
||||
wantsKeys: () => true,
|
||||
|
|
|
@ -24,6 +24,7 @@ export function createMockWebIntegration() {
|
|||
getService: () => MozServices.Default,
|
||||
isSync: () => false,
|
||||
wantsKeys: () => false,
|
||||
isDesktopSync: () => false,
|
||||
data: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import LoadingSpinner from 'fxa-react/components/LoadingSpinner';
|
|||
import VerificationMethods from '../../../constants/verification-methods';
|
||||
import {
|
||||
Integration,
|
||||
isOAuthIntegration,
|
||||
useAuthClient,
|
||||
useFtlMsgResolver,
|
||||
useSensitiveDataClient,
|
||||
|
@ -61,8 +60,7 @@ const SigninUnblockContainer = ({
|
|||
|
||||
const { email, hasLinkedAccount, hasPassword } = location.state || {};
|
||||
|
||||
const wantsTwoStepAuthentication =
|
||||
isOAuthIntegration(integration) && integration.wantsTwoStepAuthentication();
|
||||
const wantsTwoStepAuthentication = integration.wantsTwoStepAuthentication();
|
||||
|
||||
const { finishOAuthFlowHandler, oAuthDataError } = useFinishOAuthFlowHandler(
|
||||
authClient,
|
||||
|
|
|
@ -72,17 +72,29 @@ function mockSyncDesktopV3Integration() {
|
|||
isSync: () => true,
|
||||
wantsKeys: () => true,
|
||||
data: { service: 'sync' },
|
||||
isDesktopSync: () => true,
|
||||
} as Integration;
|
||||
}
|
||||
function mockSyncOAuthIntegration(
|
||||
function mockOAuthWebIntegration(
|
||||
{ data }: { data?: { service?: string } } = { data: { service: 'sync' } }
|
||||
) {
|
||||
integration = {
|
||||
type: IntegrationType.OAuth,
|
||||
type: IntegrationType.OAuthWeb,
|
||||
getService: () => MozServices.Monitor,
|
||||
isSync: () => false,
|
||||
wantsKeys: () => true,
|
||||
data,
|
||||
isDesktopSync: () => false,
|
||||
} as Integration;
|
||||
}
|
||||
|
||||
function mockOAuthNativeIntegration() {
|
||||
integration = {
|
||||
type: IntegrationType.OAuthNative,
|
||||
getService: () => 'sync',
|
||||
isSync: () => true,
|
||||
wantsKeys: () => true,
|
||||
data,
|
||||
isDesktopSync: () => true,
|
||||
} as Integration;
|
||||
}
|
||||
|
||||
|
@ -92,6 +104,7 @@ function mockWebIntegration() {
|
|||
getService: () => MozServices.Default,
|
||||
isSync: () => false,
|
||||
wantsKeys: () => false,
|
||||
isDesktopSync: () => false,
|
||||
data: {},
|
||||
} as Integration;
|
||||
}
|
||||
|
@ -623,8 +636,8 @@ describe('signin container', () => {
|
|||
expect(firefox.fxaCanLinkAccount).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it('is not called when conditions are not met (oauth integration)', async () => {
|
||||
mockSyncOAuthIntegration({ data: {} });
|
||||
it('is not called when conditions are not met (oauth web integration)', async () => {
|
||||
mockOAuthWebIntegration({ data: {} });
|
||||
(firefox.fxaCanLinkAccount as jest.Mock).mockImplementationOnce(
|
||||
async () => ({
|
||||
ok: true,
|
||||
|
@ -640,8 +653,8 @@ describe('signin container', () => {
|
|||
expect(firefox.fxaCanLinkAccount).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it('calls fxaCanLinkAccount when conditions are met (oauth integration)', async () => {
|
||||
mockSyncOAuthIntegration();
|
||||
it('calls fxaCanLinkAccount when conditions are met (oauth native integration, isSyncDesktop)', async () => {
|
||||
mockOAuthNativeIntegration();
|
||||
(firefox.fxaCanLinkAccount as jest.Mock).mockImplementationOnce(
|
||||
async () => ({
|
||||
ok: true,
|
||||
|
|
|
@ -248,9 +248,7 @@ const SigninContainer = ({
|
|||
// warning. The browser will automatically respond with { ok: true } without
|
||||
// prompting the user if it matches the email the browser has data for.
|
||||
if (
|
||||
// NOTE, SYNC-4408 OAuth desktop needs to add `service=sync` as a query parameter
|
||||
// for this to work for OAuth desktop
|
||||
integration.data.service === 'sync' &&
|
||||
integration.isDesktopSync() &&
|
||||
queryParamModel.hasLinkedAccount === undefined
|
||||
) {
|
||||
const { ok } = await firefox.fxaCanLinkAccount({ email });
|
||||
|
@ -306,11 +304,7 @@ const SigninContainer = ({
|
|||
if (
|
||||
'data' in result &&
|
||||
result.data &&
|
||||
// NOTE, Oauth desktop needs to add `service=sync` as a query parameter for this
|
||||
// to take users to the inline recovery key flow (SYNC-4408). (We may want
|
||||
// check for client ID to determine oauth desktop instead, TBD slight refactor for
|
||||
// FXA-10313).
|
||||
options.service === 'sync' &&
|
||||
integration.isDesktopSync() &&
|
||||
config.featureFlags?.recoveryCodeSetupOnSyncSignIn === true &&
|
||||
localStorage.getItem(
|
||||
Constants.DISABLE_PROMO_ACCOUNT_RECOVERY_KEY_DO_IT_LATER
|
||||
|
|
|
@ -37,7 +37,6 @@ import {
|
|||
} from '../../models/integrations/client-matching';
|
||||
import firefox from '../../lib/channels/firefox';
|
||||
import { navigate } from '@reach/router';
|
||||
import { sessionToken } from '../../lib/cache';
|
||||
import { IntegrationType } from '../../models';
|
||||
|
||||
// import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
|
||||
|
|
|
@ -23,10 +23,10 @@ import GleanMetrics from '../../lib/glean';
|
|||
import { usePageViewEvent } from '../../lib/metrics';
|
||||
import { StoredAccountData, storeAccountData } from '../../lib/storage-utils';
|
||||
import {
|
||||
isOAuthIntegration,
|
||||
useSensitiveDataClient,
|
||||
useFtlMsgResolver,
|
||||
isWebIntegration,
|
||||
isOAuthIntegration,
|
||||
} from '../../models';
|
||||
import {
|
||||
isClientMonitor,
|
||||
|
|
|
@ -19,7 +19,10 @@ export interface AvatarResponse {
|
|||
}
|
||||
|
||||
export type SigninIntegration =
|
||||
| Pick<Integration, 'type' | 'isSync' | 'getService' | 'wantsKeys' | 'data'>
|
||||
| Pick<
|
||||
Integration,
|
||||
'type' | 'isSync' | 'getService' | 'wantsKeys' | 'data' | 'isDesktopSync'
|
||||
>
|
||||
| SigninOAuthIntegration;
|
||||
|
||||
export type SigninOAuthIntegration = Pick<
|
||||
|
@ -31,6 +34,7 @@ export type SigninOAuthIntegration = Pick<
|
|||
| 'wantsKeys'
|
||||
| 'wantsLogin'
|
||||
| 'data'
|
||||
| 'isDesktopSync'
|
||||
>;
|
||||
|
||||
export interface LocationState {
|
||||
|
|
|
@ -104,11 +104,12 @@ export function createMockSigninWebIntegration(): SigninIntegration {
|
|||
getService: () => MozServices.Default,
|
||||
wantsKeys: () => false,
|
||||
data: {},
|
||||
isDesktopSync: () => false,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockSigninSyncIntegration(
|
||||
type = IntegrationType.OAuth
|
||||
type = IntegrationType.OAuthNative
|
||||
): SigninIntegration {
|
||||
return {
|
||||
type,
|
||||
|
@ -116,6 +117,7 @@ export function createMockSigninSyncIntegration(
|
|||
wantsKeys: () => true,
|
||||
getService: () => MozServices.FirefoxSync,
|
||||
data: {},
|
||||
isDesktopSync: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -129,12 +131,13 @@ export function createMockSigninOAuthIntegration({
|
|||
isSync?: boolean;
|
||||
} = {}): SigninOAuthIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuth,
|
||||
type: IntegrationType.OAuthWeb,
|
||||
getService: () => clientId || MOCK_CLIENT_ID,
|
||||
isSync: () => isSync,
|
||||
wantsKeys: () => wantsKeys,
|
||||
wantsLogin: () => false,
|
||||
wantsTwoStepAuthentication: () => false,
|
||||
isDesktopSync: () => isSync,
|
||||
data: {},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ function applyMocks() {
|
|||
jest.restoreAllMocks();
|
||||
|
||||
integration = {
|
||||
type: ModelsModule.IntegrationType.OAuth,
|
||||
type: ModelsModule.IntegrationType.OAuthWeb,
|
||||
} as Integration;
|
||||
jest
|
||||
.spyOn(ConfirmSignupCodeModule, 'default')
|
||||
|
|
|
@ -15,10 +15,15 @@ import {
|
|||
MOCK_AUTH_ERROR,
|
||||
MOCK_SIGNUP_CODE,
|
||||
Subject,
|
||||
createMockOAuthIntegration,
|
||||
createMockOAuthNativeIntegration,
|
||||
createMockOAuthWebIntegration,
|
||||
createMockWebIntegration,
|
||||
} from './mocks';
|
||||
import { MOCK_STORED_ACCOUNT } from '../../mocks';
|
||||
import {
|
||||
MOCK_OAUTH_FLOW_HANDLER_RESPONSE,
|
||||
MOCK_STORED_ACCOUNT,
|
||||
mockFinishOAuthFlowHandler,
|
||||
} from '../../mocks';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
import { useWebRedirect } from '../../../lib/hooks/useWebRedirect';
|
||||
import { ConfirmSignupCodeIntegration } from './interfaces';
|
||||
|
@ -28,6 +33,7 @@ import {
|
|||
tryAgainError,
|
||||
} from '../../../lib/oauth/hooks';
|
||||
import { OAUTH_ERRORS } from '../../../lib/oauth';
|
||||
import firefox from '../../../lib/channels/firefox';
|
||||
|
||||
jest.mock('../../../lib/metrics', () => ({
|
||||
usePageViewEvent: jest.fn(),
|
||||
|
@ -80,15 +86,27 @@ function renderWithSession({
|
|||
newsletterSlugs,
|
||||
integration,
|
||||
finishOAuthFlowHandler,
|
||||
offeredSyncEngines,
|
||||
declinedSyncEngines,
|
||||
}: {
|
||||
session?: Session;
|
||||
newsletterSlugs?: string[];
|
||||
integration?: ConfirmSignupCodeIntegration;
|
||||
finishOAuthFlowHandler?: FinishOAuthFlowHandler;
|
||||
offeredSyncEngines?: string[];
|
||||
declinedSyncEngines?: string[];
|
||||
}) {
|
||||
renderWithLocalizationProvider(
|
||||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject {...{ newsletterSlugs, integration, finishOAuthFlowHandler }} />
|
||||
<Subject
|
||||
{...{
|
||||
newsletterSlugs,
|
||||
integration,
|
||||
finishOAuthFlowHandler,
|
||||
offeredSyncEngines,
|
||||
declinedSyncEngines,
|
||||
}}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
@ -205,8 +223,12 @@ describe('ConfirmSignupCode page', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('OAuth integration', () => {
|
||||
const integration = createMockOAuthIntegration();
|
||||
describe('OAuth web integration', () => {
|
||||
let fxaOAuthLoginSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
fxaOAuthLoginSpy = jest.spyOn(firefox, 'fxaOAuthLogin');
|
||||
});
|
||||
const integration = createMockOAuthWebIntegration();
|
||||
|
||||
it('shows an error banner for an OAuth error', async () => {
|
||||
renderWithSession({
|
||||
|
@ -220,6 +242,49 @@ describe('ConfirmSignupCode page', () => {
|
|||
screen.getByText(OAUTH_ERRORS.TRY_AGAIN.message);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send web channel messages', async () => {
|
||||
renderWithSession({
|
||||
session,
|
||||
integration,
|
||||
finishOAuthFlowHandler: jest.fn(),
|
||||
});
|
||||
submit();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fxaOAuthLoginSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OAuth native integration', () => {
|
||||
let fxaOAuthLoginSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
fxaOAuthLoginSpy = jest.spyOn(firefox, 'fxaOAuthLogin');
|
||||
});
|
||||
const integration = createMockOAuthNativeIntegration();
|
||||
|
||||
it('sends expected web channel messages', async () => {
|
||||
const offeredSyncEngines = ['blabbitybee', 'bloopitybop'];
|
||||
const declinedSyncEngines = ['bloopitybop'];
|
||||
renderWithSession({
|
||||
session,
|
||||
integration,
|
||||
finishOAuthFlowHandler: mockFinishOAuthFlowHandler,
|
||||
declinedSyncEngines,
|
||||
offeredSyncEngines,
|
||||
});
|
||||
submit();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fxaOAuthLoginSpy).toHaveBeenCalledWith({
|
||||
declinedSyncEngines,
|
||||
offeredSyncEngines,
|
||||
action: 'signup',
|
||||
...MOCK_OAUTH_FLOW_HANDLER_RESPONSE,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Web integration on submission', () => {
|
||||
|
|
|
@ -4,14 +4,7 @@
|
|||
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { FinishOAuthFlowHandler } from '../../../lib/oauth/hooks';
|
||||
import {
|
||||
BaseIntegration,
|
||||
BaseIntegrationData,
|
||||
Integration,
|
||||
IntegrationType,
|
||||
OAuthIntegration,
|
||||
OAuthIntegrationData,
|
||||
} from '../../../models';
|
||||
import { Integration, OAuthWebIntegration } from '../../../models';
|
||||
import { StoredAccountData } from '../../../lib/storage-utils';
|
||||
import { QueryParams } from '../../..';
|
||||
|
||||
|
@ -49,33 +42,25 @@ export interface ConfirmSignupCodeFormData {
|
|||
code: string;
|
||||
}
|
||||
|
||||
export interface ConfirmSignupCodeBaseIntegration {
|
||||
type: IntegrationType;
|
||||
data: {
|
||||
uid: BaseIntegrationData['uid'];
|
||||
redirectTo: BaseIntegrationData['redirectTo'];
|
||||
};
|
||||
getService: BaseIntegration['getService'];
|
||||
}
|
||||
export type ConfirmSignupCodeBaseIntegration = Pick<
|
||||
Integration,
|
||||
'type' | 'data' | 'getService'
|
||||
>;
|
||||
|
||||
export interface ConfirmSignupCodeOAuthIntegration {
|
||||
type: IntegrationType.OAuth;
|
||||
data: {
|
||||
uid: OAuthIntegrationData['uid'];
|
||||
redirectTo: OAuthIntegrationData['redirectTo'];
|
||||
};
|
||||
getService: () => ReturnType<OAuthIntegration['getService']>;
|
||||
getRedirectUri: () => ReturnType<OAuthIntegration['getRedirectUri']>;
|
||||
wantsTwoStepAuthentication: () => ReturnType<
|
||||
OAuthIntegration['wantsTwoStepAuthentication']
|
||||
>;
|
||||
isSync: () => ReturnType<OAuthIntegration['isSync']>;
|
||||
getPermissions: () => ReturnType<OAuthIntegration['getPermissions']>;
|
||||
}
|
||||
export type ConfirmSignupCodeOAuthIntegration = Pick<
|
||||
OAuthWebIntegration,
|
||||
| 'type'
|
||||
| 'data'
|
||||
| 'getService'
|
||||
| 'getRedirectUri'
|
||||
| 'wantsTwoStepAuthentication'
|
||||
| 'isSync'
|
||||
| 'getPermissions'
|
||||
>;
|
||||
|
||||
export type ConfirmSignupCodeIntegration =
|
||||
| ConfirmSignupCodeOAuthIntegration
|
||||
| ConfirmSignupCodeBaseIntegration;
|
||||
| ConfirmSignupCodeBaseIntegration
|
||||
| ConfirmSignupCodeOAuthIntegration;
|
||||
|
||||
export interface GetEmailBounceStatusResponse {
|
||||
emailBounceStatus: { hasHardBounce: boolean };
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import React from 'react';
|
||||
import { LocationProvider } from '@reach/router';
|
||||
import ConfirmSignupCode from '.';
|
||||
import { IntegrationType } from '../../../models';
|
||||
import { IntegrationType, OAuthNativeClients } from '../../../models';
|
||||
import {
|
||||
MOCK_EMAIL,
|
||||
MOCK_FLOW_ID,
|
||||
|
@ -42,17 +42,29 @@ export function createMockWebIntegration({
|
|||
};
|
||||
}
|
||||
|
||||
export function createMockOAuthIntegration(
|
||||
export function createMockOAuthWebIntegration(
|
||||
serviceName = MOCK_SERVICE
|
||||
): ConfirmSignupCodeOAuthIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuth,
|
||||
type: IntegrationType.OAuthWeb,
|
||||
data: { uid: MOCK_UID, redirectTo: undefined },
|
||||
getRedirectUri: () => MOCK_REDIRECT_URI,
|
||||
getService: () => serviceName,
|
||||
wantsTwoStepAuthentication: () => false,
|
||||
getPermissions: () => [],
|
||||
isSync: () => false,
|
||||
getPermissions: () => [],
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockOAuthNativeIntegration(): ConfirmSignupCodeOAuthIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuthNative,
|
||||
data: { uid: MOCK_UID, redirectTo: undefined },
|
||||
getRedirectUri: () => MOCK_REDIRECT_URI,
|
||||
getService: () => OAuthNativeClients.FirefoxDesktop,
|
||||
wantsTwoStepAuthentication: () => false,
|
||||
isSync: () => true,
|
||||
getPermissions: () => [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -60,10 +72,14 @@ export const Subject = ({
|
|||
integration = createMockWebIntegration(),
|
||||
newsletterSlugs,
|
||||
finishOAuthFlowHandler = mockFinishOAuthFlowHandler,
|
||||
offeredSyncEngines,
|
||||
declinedSyncEngines,
|
||||
}: {
|
||||
integration?: ConfirmSignupCodeIntegration;
|
||||
newsletterSlugs?: string[];
|
||||
finishOAuthFlowHandler?: FinishOAuthFlowHandler;
|
||||
offeredSyncEngines?: string[];
|
||||
declinedSyncEngines?: string[];
|
||||
}) => {
|
||||
return (
|
||||
<LocationProvider>
|
||||
|
@ -72,6 +88,8 @@ export const Subject = ({
|
|||
integration,
|
||||
newsletterSlugs,
|
||||
finishOAuthFlowHandler,
|
||||
offeredSyncEngines,
|
||||
declinedSyncEngines,
|
||||
}}
|
||||
flowQueryParams={{ flowId: MOCK_FLOW_ID }}
|
||||
email={MOCK_EMAIL}
|
||||
|
|
|
@ -312,7 +312,7 @@ describe('sign-up-container', () => {
|
|||
});
|
||||
|
||||
describe('web-channel-interactions', () => {
|
||||
describe('sync desktop', () => {
|
||||
describe('SyncDesktopV3 integration', () => {
|
||||
beforeEach(() => {
|
||||
// here we override some key behaviors to alter the containers behavior
|
||||
serviceName = MozServices.FirefoxSync;
|
||||
|
@ -338,12 +338,12 @@ describe('sign-up-container', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('sync-service-on-oauth', () => {
|
||||
describe('OAuth native integration with Sync', () => {
|
||||
beforeEach(() => {
|
||||
serviceName = MozServices.FirefoxSync;
|
||||
integration.getService = () => MozServices.FirefoxSync;
|
||||
integration.isSync = () => true;
|
||||
integration.type = IntegrationType.OAuth;
|
||||
integration.type = IntegrationType.OAuthNative;
|
||||
});
|
||||
it('adds event listeners and sends', async () => {
|
||||
await render();
|
||||
|
@ -359,10 +359,11 @@ describe('sign-up-container', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('non-sync-service', () => {
|
||||
describe('Web integration, default service', () => {
|
||||
beforeEach(() => {
|
||||
integration.type = IntegrationType.Web;
|
||||
integration.getService = () => MozServices.Default;
|
||||
integration.isSync = () => false;
|
||||
});
|
||||
|
||||
it('did not add event listeners and send command', async () => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { RouteComponentProps, useLocation } from '@reach/router';
|
|||
import { useNavigateWithQuery as useNavigate } from '../../lib/hooks/useNavigateWithQuery';
|
||||
import {
|
||||
Integration,
|
||||
isOAuthIntegration,
|
||||
isOAuthNativeIntegrationSync,
|
||||
isSyncDesktopV3Integration,
|
||||
useAuthClient,
|
||||
useConfig,
|
||||
|
@ -96,10 +96,9 @@ const SignupContainer = ({
|
|||
string[] | undefined
|
||||
>();
|
||||
|
||||
const isOAuth = isOAuthIntegration(integration);
|
||||
const isSyncOAuth = isOAuth && integration.isSync();
|
||||
const isSyncOAuth = isOAuthNativeIntegrationSync(integration);
|
||||
const isSyncDesktopV3 = isSyncDesktopV3Integration(integration);
|
||||
const isSyncWebChannel = isSyncOAuth || isSyncDesktopV3;
|
||||
const isSync = integration.isSync();
|
||||
const wantsKeys = integration.wantsKeys();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -141,7 +140,7 @@ const SignupContainer = ({
|
|||
// that we listen for.
|
||||
// TODO: In content-server, we send this on app-start for all integration types.
|
||||
// Do we want to move this somewhere else once the index page is Reactified?
|
||||
if (isSyncWebChannel) {
|
||||
if (isSync) {
|
||||
(async () => {
|
||||
const status = await firefox.fxaStatus({
|
||||
// TODO: Improve getting 'context', probably set this on the integration
|
||||
|
@ -154,6 +153,8 @@ const SignupContainer = ({
|
|||
if (!webChannelEngines && status.capabilities.engines) {
|
||||
// choose_what_to_sync may be disabled for mobile sync, see:
|
||||
// https://github.com/mozilla/application-services/issues/1761
|
||||
// Desktop OAuth Sync will always provide this capability too
|
||||
// for consistency.
|
||||
if (
|
||||
isSyncDesktopV3 ||
|
||||
(isSyncOAuth && status.capabilities.choose_what_to_sync)
|
||||
|
@ -163,7 +164,7 @@ const SignupContainer = ({
|
|||
}
|
||||
})();
|
||||
}
|
||||
}, [isSyncWebChannel, isSyncDesktopV3, isSyncOAuth, webChannelEngines]);
|
||||
}, [isSync, isSyncDesktopV3, isSyncOAuth, webChannelEngines]);
|
||||
|
||||
const [beginSignup] = useMutation<BeginSignupResponse>(BEGIN_SIGNUP_MUTATION);
|
||||
|
||||
|
@ -272,8 +273,6 @@ const SignupContainer = ({
|
|||
queryParamModel,
|
||||
beginSignupHandler,
|
||||
webChannelEngines,
|
||||
isSyncWebChannel,
|
||||
isSyncOAuth,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@ import { LocationProvider } from '@reach/router';
|
|||
import { Meta } from '@storybook/react';
|
||||
import { withLocalization } from 'fxa-react/lib/storybooks';
|
||||
import {
|
||||
createMockSignupOAuthIntegration,
|
||||
createMockSignupOAuthWebIntegration,
|
||||
createMockSignupSyncDesktopV3Integration,
|
||||
mockBeginSignupHandler,
|
||||
signupQueryParams,
|
||||
|
@ -21,10 +21,6 @@ import {
|
|||
POCKET_CLIENTIDS,
|
||||
} from '../../models/integrations/client-matching';
|
||||
import { getSyncEngineIds } from '../../components/ChooseWhatToSync/sync-engines';
|
||||
import {
|
||||
isSyncOAuthIntegration,
|
||||
isSyncDesktopV3Integration,
|
||||
} from '../../models';
|
||||
import { MOCK_CLIENT_ID } from '../mocks';
|
||||
|
||||
export default {
|
||||
|
@ -37,10 +33,8 @@ const urlQueryData = mockUrlQueryData(signupQueryParams);
|
|||
const queryParamModel = new SignupQueryParams(urlQueryData);
|
||||
|
||||
const storyWithProps = (
|
||||
integration: SignupIntegration = createMockSignupOAuthIntegration()
|
||||
integration: SignupIntegration = createMockSignupOAuthWebIntegration()
|
||||
) => {
|
||||
const isSyncOAuth = isSyncOAuthIntegration(integration);
|
||||
|
||||
const story = () => (
|
||||
<LocationProvider>
|
||||
<Signup
|
||||
|
@ -49,9 +43,6 @@ const storyWithProps = (
|
|||
queryParamModel,
|
||||
beginSignupHandler: mockBeginSignupHandler,
|
||||
webChannelEngines: getSyncEngineIds(),
|
||||
isSyncWebChannel:
|
||||
isSyncOAuth || isSyncDesktopV3Integration(integration),
|
||||
isSyncOAuth,
|
||||
}}
|
||||
/>
|
||||
</LocationProvider>
|
||||
|
@ -64,11 +55,11 @@ export const Default = storyWithProps();
|
|||
export const CantChangeEmail = storyWithProps();
|
||||
|
||||
export const ClientIsPocket = storyWithProps(
|
||||
createMockSignupOAuthIntegration(POCKET_CLIENTIDS[0])
|
||||
createMockSignupOAuthWebIntegration(POCKET_CLIENTIDS[0])
|
||||
);
|
||||
|
||||
export const ClientIsMonitor = storyWithProps(
|
||||
createMockSignupOAuthIntegration(MONITOR_CLIENTIDS[0])
|
||||
createMockSignupOAuthWebIntegration(MONITOR_CLIENTIDS[0])
|
||||
);
|
||||
|
||||
export const SyncDesktopV3 = storyWithProps(
|
||||
|
@ -76,5 +67,5 @@ export const SyncDesktopV3 = storyWithProps(
|
|||
);
|
||||
|
||||
export const SyncOAuth = storyWithProps(
|
||||
createMockSignupOAuthIntegration(MOCK_CLIENT_ID, true)
|
||||
createMockSignupOAuthWebIntegration(MOCK_CLIENT_ID)
|
||||
);
|
||||
|
|
|
@ -20,11 +20,11 @@ import {
|
|||
BEGIN_SIGNUP_HANDLER_RESPONSE,
|
||||
MOCK_SEARCH_PARAMS,
|
||||
Subject,
|
||||
createMockSignupOAuthIntegration,
|
||||
createMockSignupOAuthWebIntegration,
|
||||
createMockSignupOAuthNativeIntegration,
|
||||
createMockSignupSyncDesktopV3Integration,
|
||||
} from './mocks';
|
||||
import {
|
||||
MOCK_CLIENT_ID,
|
||||
MOCK_EMAIL,
|
||||
MOCK_KEY_FETCH_TOKEN,
|
||||
MOCK_PASSWORD,
|
||||
|
@ -178,7 +178,7 @@ describe('Signup page', () => {
|
|||
it('shows an info banner and Pocket-specific TOS when client is Pocket', async () => {
|
||||
renderWithLocalizationProvider(
|
||||
<Subject
|
||||
integration={createMockSignupOAuthIntegration(POCKET_CLIENTIDS[0])}
|
||||
integration={createMockSignupOAuthWebIntegration(POCKET_CLIENTIDS[0])}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -590,11 +590,11 @@ describe('Signup page', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('on success with Sync OAuth integration', () => {
|
||||
describe('on success with OAuthNative integration with Sync', () => {
|
||||
beforeEach(() => {
|
||||
renderWithLocalizationProvider(
|
||||
<Subject
|
||||
integration={createMockSignupOAuthIntegration('', true)}
|
||||
integration={createMockSignupOAuthNativeIntegration()}
|
||||
beginSignupHandler={mockBeginSignupHandler}
|
||||
/>
|
||||
);
|
||||
|
@ -711,14 +711,14 @@ describe('Signup page', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('on success with OAuth integration', async () => {
|
||||
it('on success with OAuth Web integration', async () => {
|
||||
const mockBeginSignupHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(BEGIN_SIGNUP_HANDLER_RESPONSE);
|
||||
|
||||
renderWithLocalizationProvider(
|
||||
<Subject
|
||||
integration={createMockSignupOAuthIntegration(MOCK_CLIENT_ID)}
|
||||
integration={createMockSignupOAuthWebIntegration()}
|
||||
beginSignupHandler={mockBeginSignupHandler}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -41,6 +41,7 @@ import { StoredAccountData, storeAccountData } from '../../lib/storage-utils';
|
|||
import { MozServices } from '../../lib/types';
|
||||
import {
|
||||
isOAuthIntegration,
|
||||
isOAuthNativeIntegrationSync,
|
||||
isSyncDesktopV3Integration,
|
||||
useFtlMsgResolver,
|
||||
} from '../../models';
|
||||
|
@ -57,8 +58,6 @@ export const Signup = ({
|
|||
queryParamModel,
|
||||
beginSignupHandler,
|
||||
webChannelEngines,
|
||||
isSyncWebChannel,
|
||||
isSyncOAuth,
|
||||
}: SignupProps) => {
|
||||
usePageViewEvent(viewName, REACT_ENTRYPOINT);
|
||||
|
||||
|
@ -66,6 +65,8 @@ export const Signup = ({
|
|||
GleanMetrics.registration.view();
|
||||
}, []);
|
||||
|
||||
const isSyncOAuth = isOAuthNativeIntegrationSync(integration);
|
||||
const isSyncDesktopV3 = isSyncDesktopV3Integration(integration);
|
||||
const isSync = integration.isSync();
|
||||
const email = queryParamModel.email;
|
||||
|
||||
|
@ -111,7 +112,7 @@ export const Signup = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (webChannelEngines) {
|
||||
if (isSyncDesktopV3Integration(integration)) {
|
||||
if (isSyncDesktopV3) {
|
||||
// Desktop v3 web channel message sends additional engines
|
||||
setOfferedSyncEngineConfigs([
|
||||
...defaultDesktopV3SyncEngineConfigs,
|
||||
|
@ -119,7 +120,7 @@ export const Signup = ({
|
|||
webChannelEngines.includes(engine.id)
|
||||
),
|
||||
]);
|
||||
} else if (isSyncWebChannel) {
|
||||
} else if (isSyncOAuth) {
|
||||
// OAuth Webchannel context sends all engines
|
||||
setOfferedSyncEngineConfigs(
|
||||
syncEngineConfigs.filter((engine) =>
|
||||
|
@ -128,7 +129,7 @@ export const Signup = ({
|
|||
);
|
||||
}
|
||||
}
|
||||
}, [integration, isSyncWebChannel, webChannelEngines]);
|
||||
}, [isSyncDesktopV3, isSyncOAuth, webChannelEngines]);
|
||||
|
||||
useEffect(() => {
|
||||
if (offeredSyncEngineConfigs) {
|
||||
|
@ -244,7 +245,7 @@ export const Signup = ({
|
|||
const getOfferedSyncEngines = () =>
|
||||
getSyncEngineIds(offeredSyncEngineConfigs || []);
|
||||
|
||||
if (integration.isSync()) {
|
||||
if (isSync) {
|
||||
const syncEngines = {
|
||||
offeredEngines: getOfferedSyncEngines(),
|
||||
declinedEngines: declinedSyncEngines,
|
||||
|
@ -315,7 +316,7 @@ export const Signup = ({
|
|||
selectedNewsletterSlugs,
|
||||
declinedSyncEngines,
|
||||
email,
|
||||
integration,
|
||||
isSync,
|
||||
offeredSyncEngineConfigs,
|
||||
isSyncOAuth,
|
||||
localizedValidAgeError,
|
||||
|
@ -323,7 +324,7 @@ export const Signup = ({
|
|||
);
|
||||
|
||||
const showCWTS = () => {
|
||||
if (isSyncWebChannel) {
|
||||
if (isSync) {
|
||||
if (offeredSyncEngineConfigs) {
|
||||
return (
|
||||
<ChooseWhatToSync
|
||||
|
|
|
@ -47,14 +47,12 @@ export interface SignupProps {
|
|||
queryParamModel: SignupQueryParams;
|
||||
beginSignupHandler: BeginSignupHandler;
|
||||
webChannelEngines: string[] | undefined;
|
||||
isSyncWebChannel: boolean;
|
||||
isSyncOAuth: boolean;
|
||||
}
|
||||
|
||||
export type SignupIntegration = SignupOAuthIntegration | SignupBaseIntegration;
|
||||
|
||||
export interface SignupOAuthIntegration {
|
||||
type: IntegrationType.OAuth;
|
||||
type: IntegrationType.OAuthWeb | IntegrationType.OAuthNative;
|
||||
isSync: () => ReturnType<OAuthIntegration['isSync']>;
|
||||
getRedirectUri: () => ReturnType<OAuthIntegration['getRedirectUri']>;
|
||||
saveOAuthState: () => ReturnType<OAuthIntegration['saveOAuthState']>;
|
||||
|
|
|
@ -5,11 +5,7 @@
|
|||
import { LocationProvider } from '@reach/router';
|
||||
import Signup from '.';
|
||||
import { MozServices } from '../../lib/types';
|
||||
import {
|
||||
IntegrationType,
|
||||
isSyncDesktopV3Integration,
|
||||
isSyncOAuthIntegration,
|
||||
} from '../../models';
|
||||
import { IntegrationType } from '../../models';
|
||||
import { mockUrlQueryData } from '../../models/mocks';
|
||||
import { SignupQueryParams } from '../../models/pages/signup';
|
||||
import {
|
||||
|
@ -50,12 +46,24 @@ export function createMockSignupSyncDesktopV3Integration(): SignupBaseIntegratio
|
|||
};
|
||||
}
|
||||
|
||||
export function createMockSignupOAuthIntegration(
|
||||
clientId?: string,
|
||||
isSync = false
|
||||
export function createMockSignupOAuthWebIntegration(
|
||||
clientId?: string
|
||||
): SignupOAuthIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuth,
|
||||
type: IntegrationType.OAuthWeb,
|
||||
getRedirectUri: () => MOCK_REDIRECT_URI,
|
||||
saveOAuthState: () => {},
|
||||
getService: () => clientId || MOCK_CLIENT_ID,
|
||||
isSync: () => false,
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockSignupOAuthNativeIntegration(
|
||||
clientId?: string,
|
||||
isSync = true
|
||||
): SignupOAuthIntegration {
|
||||
return {
|
||||
type: IntegrationType.OAuthNative,
|
||||
getRedirectUri: () => MOCK_REDIRECT_URI,
|
||||
saveOAuthState: () => {},
|
||||
getService: () => clientId || MOCK_CLIENT_ID,
|
||||
|
@ -118,7 +126,6 @@ export const Subject = ({
|
|||
}) => {
|
||||
const urlQueryData = mockUrlQueryData(queryParams);
|
||||
const queryParamModel = new SignupQueryParams(urlQueryData);
|
||||
const isSyncOAuth = isSyncOAuthIntegration(integration);
|
||||
return (
|
||||
<LocationProvider>
|
||||
<Signup
|
||||
|
@ -126,9 +133,6 @@ export const Subject = ({
|
|||
integration,
|
||||
queryParamModel,
|
||||
beginSignupHandler,
|
||||
isSyncOAuth,
|
||||
isSyncWebChannel:
|
||||
isSyncOAuth || isSyncDesktopV3Integration(integration),
|
||||
webChannelEngines: getSyncEngineIds(),
|
||||
}}
|
||||
/>
|
||||
|
|
Загрузка…
Ссылка в новой задаче