зеркало из https://github.com/mozilla/fxa.git
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:
Родитель
0973e18492
Коммит
b99f1468bd
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче