feat(auth): Update 'sync' service referes to account for 'relay'

Because:

* Sign in to the browser will be possible for Relay in addition to Sync

This commit:

* Account for service='relay' where we previously only expected 'sync'
* Update the service passed from fxa-settings to auth to account for either service or clientId as used in oauth_client_info
* Update tests

Closes #FXA-10416
This commit is contained in:
Valerie Pomerleau 2024-10-16 13:18:39 -07:00
Родитель 0973e18492
Коммит b99f1468bd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 33A451F0BB2180B4
31 изменённых файлов: 211 добавлений и 125 удалений

Просмотреть файл

@ -249,6 +249,8 @@ export class AccountHandler {
private setMetricsFlowCompleteSignal(request: AuthRequest, service?: string) {
let flowCompleteSignal;
// 'account.signed' is only used for 'sync'
// use the default for browser sign-ins that are not sync (e.g., service=relay)
if (service === 'sync') {
flowCompleteSignal = 'account.signed';
} else {

Просмотреть файл

@ -65,8 +65,16 @@ module.exports = (log, db, mailer, push, verificationReminders, glean) => {
// Our post-verification email is very specific to sync,
// so only send it if we're sure this is for sync or sync scoped client.
// Do not send for browser sign-ins that are not sync.
const scopeSet = ScopeSet.fromArray(scopes);
if (service === 'sync' || scopeSet.intersects(NOTIFICATION_SCOPES)) {
if (
service === 'sync' ||
// if legacy sync scope is included, only consider the service
// to be sync if there is no service specified
// (we already accounted for service === 'sync' above,
// so any other service will not be sync)
(scopeSet.intersects(NOTIFICATION_SCOPES) && !service)
) {
const onMobileDevice = request.app.ua.deviceType === 'mobile';
const mailOptions = {
acceptLanguage: request.app.acceptLanguage,

Просмотреть файл

@ -552,8 +552,7 @@ module.exports = function (log, config, bounces) {
'X-Verify-Code': message.code,
};
const clientInfo = await oauthClientInfo.fetch(message.service);
const serviceName = clientInfo.name;
const { name: serviceName } = await oauthClientInfo.fetch(message.service);
return this.send({
...message,
@ -771,7 +770,7 @@ module.exports = function (log, config, bounces) {
});
};
Mailer.prototype.verifyLoginEmail = function (message) {
Mailer.prototype.verifyLoginEmail = async function (message) {
log.trace('mailer.verifyLoginEmail', {
email: message.email,
uid: message.uid,
@ -805,40 +804,31 @@ module.exports = function (log, config, bounces) {
'X-Verify-Code': message.code,
};
return oauthClientInfo.fetch(message.service).then((clientInfo) => {
let clientName = clientInfo.name;
const [time, date] = this._constructLocalTimeString(
message.timeZone,
message.acceptLanguage
);
const { name: clientName } = await oauthClientInfo.fetch(message.service);
/**
* Edge case. We assume the service is firefox, which is true when the service is sync. However, if
* the service is not sync we must be more general. A user could be signing directly into settings.
*/
if (clientName === 'Firefox' && message.service !== 'sync') {
clientName = 'Mozilla';
}
const [time, date] = this._constructLocalTimeString(
message.timeZone,
message.acceptLanguage
);
return this.send({
...message,
headers,
template: templateName,
templateValues: {
clientName,
date,
device: this._formatUserAgentInfo(message),
email: message.email,
link: links.link,
oneClickLink: links.oneClickLink,
passwordChangeLink: links.passwordChangeLink,
passwordChangeLinkAttributes: links.passwordChangeLinkAttributes,
privacyUrl: links.privacyUrl,
supportLinkAttributes: links.supportLinkAttributes,
supportUrl: links.supportUrl,
time,
},
});
return this.send({
...message,
headers,
template: templateName,
templateValues: {
clientName,
date,
device: this._formatUserAgentInfo(message),
email: message.email,
link: links.link,
oneClickLink: links.oneClickLink,
passwordChangeLink: links.passwordChangeLink,
passwordChangeLinkAttributes: links.passwordChangeLinkAttributes,
privacyUrl: links.privacyUrl,
supportLinkAttributes: links.supportLinkAttributes,
supportUrl: links.supportUrl,
time,
},
});
};
@ -879,15 +869,7 @@ module.exports = function (log, config, bounces) {
'X-Signin-Verify-Code': message.code,
};
let { name: serviceName } = await oauthClientInfo.fetch(message.service);
/**
* Edge case. We assume the service is firefox, which is true when the service is sync. However, if
* the service is not sync we must be more general. A user could be signing directly into settings.
*/
if (serviceName === 'Firefox' && message.service !== 'sync') {
serviceName = 'Mozilla';
}
const { name: serviceName } = await oauthClientInfo.fetch(message.service);
return this.send({
...message,
@ -1230,7 +1212,7 @@ module.exports = function (log, config, bounces) {
});
};
Mailer.prototype.newDeviceLoginEmail = function (message) {
Mailer.prototype.newDeviceLoginEmail = async function (message) {
log.trace('mailer.newDeviceLoginEmail', {
email: message.email,
uid: message.uid,
@ -1242,39 +1224,28 @@ module.exports = function (log, config, bounces) {
'X-Link': links.passwordChangeLink,
};
return oauthClientInfo.fetch(message.service).then((clientInfo) => {
let clientName = clientInfo.name;
const [time, date] = this._constructLocalTimeString(
message.timeZone,
message.acceptLanguage
);
const { name: clientName } = await oauthClientInfo.fetch(message.service);
const [time, date] = this._constructLocalTimeString(
message.timeZone,
message.acceptLanguage
);
/**
* Edge case. We assume the service is firefox, which is true when the service is sync. However, if
* the service is not sync we must be more general. A user could be signing directly into settings.
*/
if (clientName === 'Firefox' && message.service !== 'sync') {
clientName = 'Mozilla';
}
return this.send({
...message,
headers,
template: templateName,
templateValues: {
clientName,
date,
device: this._formatUserAgentInfo(message),
link: links.link,
passwordChangeLink: links.passwordChangeLink,
passwordChangeLinkAttributes: links.passwordChangeLinkAttributes,
privacyUrl: links.privacyUrl,
supportLinkAttributes: links.supportLinkAttributes,
supportUrl: links.supportUrl,
time,
},
});
return this.send({
...message,
headers,
template: templateName,
templateValues: {
clientName,
date,
device: this._formatUserAgentInfo(message),
link: links.link,
passwordChangeLink: links.passwordChangeLink,
passwordChangeLinkAttributes: links.passwordChangeLinkAttributes,
privacyUrl: links.privacyUrl,
supportLinkAttributes: links.supportLinkAttributes,
supportUrl: links.supportUrl,
time,
},
});
};

Просмотреть файл

@ -13,6 +13,10 @@ module.exports = (log, config) => {
const FIREFOX_CLIENT = {
name: 'Firefox',
};
const MOZILLA_CLIENT = {
name: 'Mozilla',
};
// TODO: prob don't need this cache anymore now that it's just a db call
const clientCache = new Keyv({
ttl: OAUTH_CLIENT_INFO_CACHE_TTL,
@ -22,18 +26,29 @@ module.exports = (log, config) => {
/**
* Fetches OAuth client info from the OAuth server.
* Stores the data into server memory.
* @param clientId
* @param service
* @returns {Promise<any>}
*/
async function fetch(clientId) {
async function fetch(service) {
log.trace('fetch.start');
if (!clientId || clientId === 'sync') {
log.trace('fetch.sync');
// Default to 'Mozilla' if the service is undefined
// If the service is undefined for a sync sign-in (where scope is provided but not service),
// this may result in some edge cases where the client name is 'Mozilla' instead of 'Firefox' in emails
// however, defaulting to Mozilla works best for web sign-ins with other browsers (e.g. Chrome)
// We might want to consider passing in scopes as well to more accurately determine the client name
if (!service) {
log.trace('fetch.noService');
return MOZILLA_CLIENT;
}
// Set the client name to 'Firefox' if the service is browser-based
if (service === 'sync' || service === 'relay') {
log.trace('fetch.firefoxClient');
return FIREFOX_CLIENT;
}
const cachedRecord = await clientCache.get(clientId);
const cachedRecord = await clientCache.get(service);
if (cachedRecord) {
// used the cachedRecord if it exists
log.trace('fetch.usedCache');
@ -42,21 +57,22 @@ module.exports = (log, config) => {
let clientInfo;
try {
clientInfo = await client.getClientById(clientId);
clientInfo = await client.getClientById(service);
} catch (err) {
// fallback to the Firefox client if request fails
if (!err.statusCode) {
log.fatal('fetch.failed', { err });
} else {
log.warn('fetch.failedForClient', { clientId });
log.warn('fetch.failedForClient', { service });
}
return FIREFOX_CLIENT;
// default to 'Mozilla' if there is an error fetching client info
return MOZILLA_CLIENT;
}
log.trace('fetch.usedServer', { body: clientInfo });
// We deliberately don't wait for this to resolve, since the
// client doesn't need to wait for us to write to the cache.
clientCache.set(clientId, clientInfo);
clientCache.set(service, clientInfo);
return clientInfo;
}

Просмотреть файл

@ -48,9 +48,9 @@ describe('lib/senders/oauth_client_info:', () => {
return clientInfo.__clientCache.clear();
});
it('returns Firefox if no client id', () => {
it('returns Mozilla if no service', () => {
return fetch().then((res) => {
assert.equal(res.name, 'Firefox');
assert.equal(res.name, 'Mozilla');
});
});
@ -60,9 +60,15 @@ describe('lib/senders/oauth_client_info:', () => {
});
});
it('falls back to Firefox if error', () => {
return fetch('0000000000000000').then((res) => {
it('returns Firefox if service=relay', () => {
return fetch('relay').then((res) => {
assert.equal(res.name, 'Firefox');
});
});
it('falls back to Mozilla if error', () => {
return fetch('0000000000000000').then((res) => {
assert.equal(res.name, 'Mozilla');
assert.ok(mockLog.fatal.calledOnce, 'called fatal log');
});
});

Просмотреть файл

@ -176,6 +176,10 @@ export abstract class Integration<
return this.data.service;
}
getClientId() {
return this.data.clientId;
}
isTrusted() {
return true;
}

Просмотреть файл

@ -174,9 +174,16 @@ describe('models/integrations/oauth-relier', function () {
});
describe('getService', () => {
it('returns clientId as service', () => {
it('returns service', () => {
model.data.modelData.set('service', 'sync');
expect(model.getService()).toBe('sync');
});
});
describe('getClientId', () => {
it('returns clientId', () => {
model.data.modelData.set('client_id', '123');
expect(model.getService()).toBe('123');
expect(model.getClientId()).toBe('123');
});
});

Просмотреть файл

@ -222,6 +222,10 @@ export class OAuthWebIntegration extends BaseIntegration {
}
getService() {
return this.data.service;
}
getClientId() {
return this.data.clientId;
}

Просмотреть файл

@ -21,7 +21,7 @@ export const Index = ({
integration,
serviceName,
}: IndexProps & RouteComponentProps) => {
const clientId = integration.getService();
const clientId = integration.getClientId();
const isSync = integration.isSync();
const isOAuth = isOAuthIntegration(integration);
const isPocketClient = isOAuth && isClientPocket(clientId);

Просмотреть файл

@ -7,7 +7,7 @@ import { Integration } from '../../models';
export type IndexIntegration = Pick<
Integration,
'type' | 'isSync' | 'getService'
'type' | 'isSync' | 'getClientId'
>;
export interface IndexProps {

Просмотреть файл

@ -5,7 +5,7 @@
import React from 'react';
import { LocationProvider } from '@reach/router';
import { MozServices } from '../../lib/types';
import { IntegrationType, OAuthIntegration } from '../../models';
import { IntegrationType } from '../../models';
import { IndexIntegration } from './interfaces';
import Index from '.';
import { MOCK_CLIENT_ID } from '../mocks';
@ -16,14 +16,14 @@ export function createMockIndexOAuthIntegration({
return {
type: IntegrationType.OAuthWeb,
isSync: () => false,
getService: () => clientId,
getClientId: () => clientId,
};
}
export function createMockIndexSyncIntegration(): IndexIntegration {
return {
type: IntegrationType.OAuthNative,
isSync: () => true,
getService: () => MOCK_CLIENT_ID,
getClientId: () => MOCK_CLIENT_ID,
};
}
@ -31,7 +31,7 @@ export function createMockIndexWebIntegration(): IndexIntegration {
return {
type: IntegrationType.Web,
isSync: () => false,
getService: () => undefined,
getClientId: () => undefined,
};
}

Просмотреть файл

@ -22,6 +22,7 @@ import InlineRecoverySetupContainer from './container';
import AuthClient from 'fxa-auth-client/browser';
import { waitFor } from '@testing-library/react';
import {
MOCK_CLIENT_ID,
MOCK_NO_TOTP,
MOCK_OAUTH_FLOW_HANDLER_RESPONSE,
MOCK_TOTP_STATUS_VERIFIED,
@ -124,7 +125,8 @@ const defaultProps = {
isSignedIn: true,
integration: {
returnOnError: () => true,
getService: () => '0123456789abcdef',
getService: () => undefined,
getClientId: () => MOCK_CLIENT_ID,
getRedirectWithErrorUrl: (error: AuthUiError) =>
`https://localhost:8080/?error=${error.errno}`,
} as unknown as OAuthIntegration,
@ -260,7 +262,7 @@ describe('InlineRecoverySetupContainer', () => {
variables: {
input: {
code: '123456',
service: '0123456789abcdef',
service: MOCK_CLIENT_ID,
},
},
});

Просмотреть файл

@ -59,8 +59,17 @@ export const InlineRecoverySetupContainer = ({
const verifyTotpHandler = useCallback(async () => {
const code = await getCode(totp!.secret);
const service = integration.getService();
const clientId = integration.getClientId();
const isBrowserClient = service === 'sync' || service === 'relay';
const result = await verifyTotp({
variables: { input: { code, service } },
variables: {
input: {
code,
...(isBrowserClient ? { service } : { service: clientId }),
},
},
});
return result.data!.verifyTotp.success;
}, [integration, totp, verifyTotp]);

Просмотреть файл

@ -22,6 +22,7 @@ export function createMockWebIntegration() {
return {
type: IntegrationType.Web,
getService: () => MozServices.Default,
getClientId: () => undefined,
isSync: () => false,
wantsKeys: () => false,
isDesktopSync: () => false,

Просмотреть файл

@ -27,7 +27,6 @@ import {
} from './interfaces';
import { hardNavigate } from 'fxa-react/lib/utils';
import { useFinishOAuthFlowHandler } from '../../../lib/oauth/hooks';
import { MozServices } from '../../../lib/types';
import { QueryParams } from '../../..';
import { queryParamsToMetricsContext } from '../../../lib/metrics';
import OAuthDataError from '../../../components/OAuthDataError';
@ -78,10 +77,17 @@ const SigninUnblockContainer = ({
signInOptions?: SignInOptions
) => {
const service = integration.getService();
const clientId = integration.getClientId();
const isBrowserClient = service === 'sync' || service === 'relay';
const options: SignInOptions = signInOptions ?? {
verificationMethod: VerificationMethods.EMAIL_OTP,
keys: integration.wantsKeys(),
...(service !== MozServices.Default && { service }),
// See oauth_client_info in the auth-server for details on service/clientId
// Sending up the clientId when the user is not signing in to the browser
// is used to show the correct service name in emails
...(isBrowserClient ? { service } : { service: clientId }),
unblockCode,
metricsContext: queryParamsToMetricsContext(
flowQueryParams as unknown as Record<string, string>

Просмотреть файл

@ -45,6 +45,7 @@ import {
mockGqlPasswordChangeFinishMutation,
mockGqlPasswordChangeStartMutation,
MOCK_FLOW_ID,
MOCK_CLIENT_ID,
} from './mocks';
import AuthClient from 'fxa-auth-client/browser';
import VerificationMethods from '../../constants/verification-methods';
@ -69,6 +70,7 @@ function mockSyncDesktopV3Integration() {
integration = {
type: IntegrationType.SyncDesktopV3,
getService: () => 'sync',
getClientId: () => undefined,
isSync: () => true,
wantsKeys: () => true,
data: { service: 'sync' },
@ -81,6 +83,7 @@ function mockOAuthWebIntegration(
integration = {
type: IntegrationType.OAuthWeb,
getService: () => MozServices.Monitor,
getClientId: () => MOCK_CLIENT_ID,
isSync: () => false,
wantsKeys: () => true,
data,
@ -92,6 +95,7 @@ function mockOAuthNativeIntegration() {
integration = {
type: IntegrationType.OAuthNative,
getService: () => 'sync',
getClientId: () => undefined,
isSync: () => true,
wantsKeys: () => true,
isDesktopSync: () => true,
@ -102,6 +106,7 @@ function mockWebIntegration() {
integration = {
type: IntegrationType.Web,
getService: () => MozServices.Default,
getClientId: () => undefined,
isSync: () => false,
wantsKeys: () => false,
isDesktopSync: () => false,

Просмотреть файл

@ -263,6 +263,9 @@ const SigninContainer = ({
}
const service = integration.getService();
const clientId = integration.getClientId();
const isBrowserClient = service === 'sync' || service === 'relay';
const { error, unverifiedAccount, v1Credentials, v2Credentials } =
await tryKeyStretchingUpgrade(
@ -278,7 +281,10 @@ const SigninContainer = ({
const options = {
verificationMethod: VerificationMethods.EMAIL_OTP,
keys: wantsKeys,
...(service !== MozServices.Default && { service }),
// See oauth_client_info in the auth-server for details on service/clientId
// Sending up the clientId when the user is not signing in to the browser
// is used to show the correct service name in emails
...(isBrowserClient ? { service } : { service: clientId }),
metricsContext: queryParamsToMetricsContext(
flowQueryParams as ReturnType<typeof searchParams>
),

Просмотреть файл

@ -1015,6 +1015,7 @@ describe('Signin', () => {
serviceName={MozServices.Pocket}
integration={createMockSigninOAuthIntegration({
clientId: POCKET_CLIENTIDS[0],
service: MozServices.Pocket,
wantsKeys: false,
})}
/>
@ -1029,6 +1030,7 @@ describe('Signin', () => {
<Subject
integration={createMockSigninOAuthIntegration({
clientId: POCKET_CLIENTIDS[0],
service: MozServices.Pocket,
})}
/>
);
@ -1058,6 +1060,7 @@ describe('Signin', () => {
<Subject
integration={createMockSigninOAuthIntegration({
clientId: MONITOR_CLIENTIDS[0],
service: MozServices.Monitor,
})}
/>
);

Просмотреть файл

@ -77,7 +77,7 @@ const Signin = ({
const [hasEngaged, setHasEngaged] = useState<boolean>(false);
const isOAuth = isOAuthIntegration(integration);
const clientId = integration.getService();
const clientId = integration.getClientId();
const isPocketClient = isOAuth && isClientPocket(clientId);
const isMonitorClient = isOAuth && isClientMonitor(clientId);
const hasLinkedAccountAndNoPassword = hasLinkedAccount && !hasPassword;

Просмотреть файл

@ -24,6 +24,7 @@ export type SigninIntegration =
| 'type'
| 'isSync'
| 'getService'
| 'getClientId'
| 'wantsKeys'
| 'data'
| 'isDesktopSync'
@ -36,6 +37,7 @@ export type SigninOAuthIntegration = Pick<
| 'type'
| 'isSync'
| 'getService'
| 'getClientId'
| 'wantsTwoStepAuthentication'
| 'wantsKeys'
| 'wantsLogin'

Просмотреть файл

@ -102,6 +102,7 @@ export function createMockSigninWebIntegration(): SigninIntegration {
type: IntegrationType.Web,
isSync: () => false,
getService: () => MozServices.Default,
getClientId: () => undefined,
wantsKeys: () => false,
data: {},
isDesktopSync: () => false,
@ -117,6 +118,7 @@ export function createMockSigninOAuthNativeSyncIntegration(
isSync: () => true,
wantsKeys: () => true,
getService: () => MozServices.FirefoxSync,
getClientId: () => MOCK_CLIENT_ID,
data: {},
isDesktopSync: () => true,
isDesktopRelay: () => false,
@ -125,16 +127,19 @@ export function createMockSigninOAuthNativeSyncIntegration(
export function createMockSigninOAuthIntegration({
clientId,
service,
wantsKeys = true,
isSync = false,
}: {
clientId?: string;
service?: MozServices;
wantsKeys?: boolean;
isSync?: boolean;
} = {}): SigninOAuthIntegration {
return {
type: IntegrationType.OAuthWeb,
getService: () => clientId || MOCK_CLIENT_ID,
getService: () => service || MozServices.Default,
getClientId: () => clientId || MOCK_CLIENT_ID,
isSync: () => isSync,
wantsKeys: () => wantsKeys,
wantsLogin: () => false,
@ -146,13 +151,15 @@ export function createMockSigninOAuthIntegration({
}
export function createMockSigninOAuthNativeIntegration({
service = 'sync',
isSync = true,
}: {
service?: string;
isSync?: boolean;
} = {}): SigninOAuthIntegration {
return {
type: IntegrationType.OAuthNative,
getService: () => MOCK_CLIENT_ID,
getService: () => service,
isSync: () => isSync,
wantsKeys: () => true,
wantsLogin: () => false,
@ -160,6 +167,7 @@ export function createMockSigninOAuthNativeIntegration({
isDesktopSync: () => isSync,
data: {},
isDesktopRelay: () => !isSync,
getClientId: () => MOCK_CLIENT_ID,
};
}

Просмотреть файл

@ -25,7 +25,7 @@ import FormVerifyCode, {
FormAttributes,
} from '../../../components/FormVerifyCode';
import { MailImage } from '../../../components/images';
import { MozServices, ResendStatus } from 'fxa-settings/src/lib/types';
import { ResendStatus } from 'fxa-settings/src/lib/types';
import {
isOAuthIntegration,
isSyncDesktopV3Integration,
@ -134,13 +134,19 @@ const ConfirmSignupCode = ({
try {
const hasSelectedNewsletters = newsletters && newsletters.length > 0;
const service = integration.getService();
const clientId = integration.getClientId();
const isBrowserClient = service === 'sync' || service === 'relay';
const options = {
...(hasSelectedNewsletters && { ...{ newsletters } }),
...(isOAuthIntegration(integration) && {
scopes: integration.getPermissions(),
}),
...(service !== MozServices.Default && { service }),
// See oauth_client_info in the auth-server for details on service/clientId
// Sending up the clientId when the user is not signing in to the browser
// is used to show the correct service name in emails
...(isBrowserClient ? { service } : { service: clientId }),
};
await session.verifySession(code, options);

Просмотреть файл

@ -44,7 +44,7 @@ export interface ConfirmSignupCodeFormData {
export type ConfirmSignupCodeBaseIntegration = Pick<
Integration,
'type' | 'data' | 'getService'
'type' | 'data' | 'getService' | 'getClientId'
>;
export type ConfirmSignupCodeOAuthIntegration = Pick<
@ -52,6 +52,7 @@ export type ConfirmSignupCodeOAuthIntegration = Pick<
| 'type'
| 'data'
| 'getService'
| 'getClientId'
| 'getRedirectUri'
| 'wantsTwoStepAuthentication'
| 'isSync'

Просмотреть файл

@ -7,6 +7,7 @@ import { LocationProvider } from '@reach/router';
import ConfirmSignupCode from '.';
import { IntegrationType, OAuthNativeClients } from '../../../models';
import {
MOCK_CLIENT_ID,
MOCK_EMAIL,
MOCK_FLOW_ID,
MOCK_KEY_FETCH_TOKEN,
@ -39,6 +40,7 @@ export function createMockWebIntegration({
type: IntegrationType.Web,
data: { uid: MOCK_UID, redirectTo },
getService: () => MozServices.Default,
getClientId: () => undefined,
};
}
@ -50,6 +52,7 @@ export function createMockOAuthWebIntegration(
data: { uid: MOCK_UID, redirectTo: undefined },
getRedirectUri: () => MOCK_REDIRECT_URI,
getService: () => serviceName,
getClientId: () => MOCK_CLIENT_ID,
wantsTwoStepAuthentication: () => false,
isSync: () => false,
getPermissions: () => [],
@ -65,6 +68,7 @@ export function createMockOAuthNativeIntegration(
data: { uid: MOCK_UID, redirectTo: undefined },
getRedirectUri: () => MOCK_REDIRECT_URI,
getService: () => OAuthNativeClients.FirefoxDesktop,
getClientId: () => MOCK_CLIENT_ID,
wantsTwoStepAuthentication: () => false,
isSync: () => isSync,
getPermissions: () => [],

Просмотреть файл

@ -62,6 +62,7 @@ function mockIntegration() {
integration = {
type: IntegrationType.SyncDesktopV3,
getService: () => MozServices.Default,
getClientId: () => undefined,
isSync: () => true,
wantsKeys: () => true,
isDesktopRelay: () => false,
@ -310,7 +311,7 @@ describe('sign-up-container', () => {
beforeEach(() => {
// here we override some key behaviors to alter the containers behavior
serviceName = MozServices.FirefoxSync;
integration.getService = () => MozServices.FirefoxSync;
integration.getService = () => 'sync';
integration.type = IntegrationType.SyncDesktopV3;
});
@ -335,7 +336,7 @@ describe('sign-up-container', () => {
describe('OAuth native integration with Sync', () => {
beforeEach(() => {
serviceName = MozServices.FirefoxSync;
integration.getService = () => MozServices.FirefoxSync;
integration.getService = () => 'sync';
integration.isSync = () => true;
integration.type = IntegrationType.OAuthNative;
});
@ -375,7 +376,7 @@ describe('sign-up-container', () => {
describe('begin-sign-up-handler', () => {
beforeEach(() => {
serviceName = MozServices.FirefoxSync;
integration.getService = () => MozServices.FirefoxSync;
integration.getService = () => 'sync';
integration.type = IntegrationType.SyncDesktopV3;
});
@ -396,7 +397,7 @@ describe('sign-up-container', () => {
options: {
verificationMethod: 'email-otp',
keys: true,
service: MozServices.FirefoxSync,
service: 'sync',
atLeast18AtReg: true,
metricsContext: {
flowId: MOCK_FLOW_ID,

Просмотреть файл

@ -29,7 +29,6 @@ import {
getKeysV2,
} from 'fxa-auth-client/lib/crypto';
import { LoadingSpinner } from 'fxa-react/components/LoadingSpinner';
import { MozServices } from '../../lib/types';
import { firefox } from '../../lib/channels/firefox';
import { Constants } from '../../lib/constants';
import { createSaltV2 } from 'fxa-auth-client/lib/salt';
@ -167,10 +166,16 @@ const SignupContainer = ({
const beginSignupHandler: BeginSignupHandler = useCallback(
async (email, password, atLeast18AtReg) => {
const service = integration.getService();
const clientId = integration.getClientId();
const isBrowserClient = service === 'sync' || service === 'relay';
const options: BeginSignUpOptions = {
verificationMethod: VerificationMethods.EMAIL_OTP,
keys: wantsKeys,
...(service !== MozServices.Default && { service }),
// See oauth_client_info in the auth-server for details on service/clientId
// Sending up the clientId when the user is not signing in to the browser
// is used to show the correct service name in emails
...(isBrowserClient ? { service } : { service: clientId }),
atLeast18AtReg,
metricsContext: queryParamsToMetricsContext(
flowQueryParams as unknown as Record<string, string>

Просмотреть файл

@ -71,5 +71,5 @@ export const SyncOAuth = storyWithProps(
);
export const OAuthDestkopServiceRelay = storyWithProps(
createMockSignupOAuthNativeIntegration(false)
createMockSignupOAuthNativeIntegration('relay', false)
);

Просмотреть файл

@ -245,7 +245,9 @@ describe('Signup page', () => {
});
it('renders as expected when service=relay', async () => {
renderWithLocalizationProvider(
<Subject integration={createMockSignupOAuthNativeIntegration(false)} />
<Subject
integration={createMockSignupOAuthNativeIntegration('relay', false)}
/>
);
// CWTS, newsletters, and third party auth should not be displayed
@ -775,7 +777,7 @@ describe('Signup page', () => {
renderWithLocalizationProvider(
<Subject
integration={createMockSignupOAuthNativeIntegration(false)}
integration={createMockSignupOAuthNativeIntegration('relay', false)}
beginSignupHandler={mockBeginSignupHandler}
/>
);

Просмотреть файл

@ -96,7 +96,7 @@ export const Signup = ({
useEffect(() => {
if (isOAuthIntegration(integration)) {
const clientId = integration.getService();
const clientId = integration.getClientId();
if (isClientPocket(clientId)) {
setClient(MozServices.Pocket);
setIsAccountSuggestionBannerVisible(true);

Просмотреть файл

@ -56,11 +56,12 @@ export type SignupOAuthIntegration = Pick<
| 'getService'
| 'isDesktopRelay'
| 'wantsKeys'
| 'getClientId'
>;
export type SignupBaseIntegration = Pick<
BaseIntegration,
'type' | 'isSync' | 'getService' | 'isDesktopRelay' | 'wantsKeys'
'type' | 'isSync' | 'getService' | 'isDesktopRelay' | 'wantsKeys' | 'getClientId'
>;
export interface SignupFormData {

Просмотреть файл

@ -34,6 +34,7 @@ export function createMockSignupWebIntegration(): SignupBaseIntegration {
return {
type: IntegrationType.Web,
getService: () => Promise.resolve(MozServices.Default),
getClientId: () => undefined,
isSync: () => false,
isDesktopRelay: () => false,
wantsKeys: () => false,
@ -44,6 +45,7 @@ export function createMockSignupSyncDesktopV3Integration(): SignupBaseIntegratio
return {
type: IntegrationType.SyncDesktopV3,
getService: () => Promise.resolve(MozServices.FirefoxSync),
getClientId: () => undefined,
isSync: () => true,
isDesktopRelay: () => false,
wantsKeys: () => false,
@ -51,13 +53,15 @@ export function createMockSignupSyncDesktopV3Integration(): SignupBaseIntegratio
}
export function createMockSignupOAuthWebIntegration(
clientId?: string
clientId?: string,
service?: MozServices
): SignupOAuthIntegration {
return {
type: IntegrationType.OAuthWeb,
getRedirectUri: () => MOCK_REDIRECT_URI,
saveOAuthState: () => {},
getService: () => clientId || MOCK_CLIENT_ID,
getService: () => service || MozServices.Default,
getClientId: () => clientId || MOCK_CLIENT_ID,
isSync: () => false,
isDesktopRelay: () => false,
wantsKeys: () => false,
@ -65,13 +69,15 @@ export function createMockSignupOAuthWebIntegration(
}
export function createMockSignupOAuthNativeIntegration(
service?: string,
isSync = true
): SignupOAuthIntegration {
return {
type: IntegrationType.OAuthNative,
getRedirectUri: () => MOCK_REDIRECT_URI,
saveOAuthState: () => {},
getService: () => MOCK_CLIENT_ID,
getService: () => service,
getClientId: () => MOCK_CLIENT_ID,
isSync: () => isSync,
isDesktopRelay: () => !isSync,
wantsKeys: () => true,