Skip to content

Commit f90a86c

Browse files
authored
Merge pull request #19981 from mozilla/FXA-12995
PART3: feat(settings): Migrate fxa-settings from GraphQL to direct auth-client (cutover)
2 parents 56c34fc + 761ed3d commit f90a86c

93 files changed

Lines changed: 2285 additions & 2967 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/functional-tests/tests/settings/changeEmail.spec.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ test.describe('severity-1 #smoke', () => {
7171
initialPassword,
7272
newPassword,
7373
target,
74-
credentials.email
74+
newEmail
7575
);
7676

7777
credentials.password = newPassword;
@@ -119,7 +119,7 @@ test.describe('severity-1 #smoke', () => {
119119
initialPassword,
120120
newPassword,
121121
target,
122-
credentials.email
122+
secondEmail
123123
);
124124

125125
credentials.password = newPassword;
@@ -130,9 +130,16 @@ test.describe('severity-1 #smoke', () => {
130130
await signin.fillOutEmailFirstForm(secondEmail);
131131
await signin.fillOutPasswordForm(newPassword);
132132

133-
// Change back the primary email again
133+
// Clear stale MFA OTP codes from the password change step
134+
await target.emailClient.clear(secondEmail);
135+
136+
// makePrimaryButton is wrapped in MfaGuard(scope="email").
137+
// After sign-out + sign-in, the JWT cache is cleared, so the MFA modal appears.
134138
await settings.secondaryEmail.makePrimaryButton.click();
135-
await settings.confirmMfaGuard(credentials.email);
139+
await settings.confirmMfaGuard(secondEmail);
140+
await expect(settings.alertBar).toHaveText(
141+
new RegExp(`${initialEmail}.*is now your primary email`)
142+
);
136143
await settings.signOut();
137144

138145
// Login with primary email and new password
@@ -141,8 +148,7 @@ test.describe('severity-1 #smoke', () => {
141148

142149
await expect(settings.settingsHeading).toBeVisible();
143150

144-
console.log('credentials.password', credentials.password);
145-
// Update which password to use the account cleanup
151+
// Update which password to use for account cleanup
146152
credentials.password = newPassword;
147153
});
148154

@@ -175,6 +181,8 @@ test.describe('severity-1 #smoke', () => {
175181
await settings.deleteAccountButton.click();
176182
await deleteAccount.deleteAccount(credentials.password);
177183

184+
await expect(page.getByText('Account deleted successfully')).toBeVisible();
185+
178186
// Try creating a new account with the same secondary email as previous account and new password
179187
await signup.fillOutEmailForm(newEmail);
180188
await signup.fillOutSignupForm(newPassword);
@@ -290,6 +298,9 @@ async function setNewPassword(
290298
): Promise<void> {
291299
await settings.password.changeButton.click();
292300

301+
// PageChangePassword is wrapped in MfaGuard(scope="password").
302+
// signUpAndPrimeMfa only primes the "email" scope JWT, so the
303+
// "password" scope JWT is never cached and the MFA modal always appears.
293304
await settings.confirmMfaGuard(email);
294305

295306
await changePassword.fillOutChangePassword(oldPassword, newPassword);

packages/functional-tests/tests/settings/multitab.spec.ts

Lines changed: 0 additions & 164 deletions
This file was deleted.

packages/functional-tests/tests/syncV3/settings.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ test.describe('severity-2 #smoke', () => {
138138
test('sign in, delete the account', async ({
139139
target,
140140
syncBrowserPages: {
141+
connectAnotherDevice,
141142
settings,
142143
deleteAccount,
143144
page,
@@ -147,22 +148,39 @@ test.describe('severity-2 #smoke', () => {
147148
testAccountTracker,
148149
}) => {
149150
const credentials = await testAccountTracker.signUpSync();
151+
const customEventDetail: LinkAccountResponse = {
152+
id: 'account_updates',
153+
message: {
154+
command: FirefoxCommand.LinkAccount,
155+
data: {
156+
ok: true,
157+
},
158+
},
159+
};
150160

151161
await page.goto(
152162
`${target.contentServerUrl}?context=fx_desktop_v3&service=sync&action=email`
153163
);
164+
await signin.respondToWebChannelMessage(customEventDetail);
154165
await signin.fillOutEmailFirstForm(credentials.email);
155166
await signin.fillOutPasswordForm(credentials.password);
156167

157168
await expect(page).toHaveURL(/signin_token_code/);
158169

170+
await signin.checkWebChannelMessage(FirefoxCommand.LinkAccount);
171+
159172
const code = await target.emailClient.getVerifyLoginCode(
160173
credentials.email
161174
);
162175
await signinTokenCode.fillOutCodeForm(code);
163-
await expect(page).toHaveURL(/pair/);
176+
177+
await signin.checkWebChannelMessage(FirefoxCommand.Login);
178+
179+
await expect(connectAnotherDevice.fxaConnected).toBeEnabled();
164180

165181
await settings.goto();
182+
await page.waitForURL(/settings/);
183+
await expect(settings.settingsHeading).toBeVisible();
166184
//Click Delete account
167185
await settings.deleteAccountButton.click();
168186
await deleteAccount.deleteAccount(credentials.password);

packages/fxa-auth-client/lib/client.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,38 @@ export type CredentialStatus = {
4747
clientSalt?: string;
4848
};
4949

50+
export interface SecurityEvent {
51+
name: string;
52+
createdAt: number;
53+
verified?: boolean;
54+
}
55+
56+
export interface AttachedClient {
57+
clientId: string;
58+
isCurrentSession: boolean;
59+
userAgent: string;
60+
deviceType: string | null;
61+
deviceId: string | null;
62+
name: string | null;
63+
lastAccessTime: number;
64+
lastAccessTimeFormatted: string;
65+
approximateLastAccessTime: number | null;
66+
approximateLastAccessTimeFormatted: string | null;
67+
location?: {
68+
city?: string | null;
69+
country?: string | null;
70+
state?: string | null;
71+
stateCode?: string | null;
72+
};
73+
os: string | null;
74+
sessionTokenId: string | null;
75+
refreshTokenId: string | null;
76+
}
77+
78+
export interface RecoveryKeyData {
79+
recoveryData: string;
80+
}
81+
5082
export type SignUpOptions = {
5183
keys?: boolean;
5284
service?: string;
@@ -1874,11 +1906,11 @@ export default class AuthClient {
18741906
return this.sessionGet('/account/sessions', sessionToken, headers);
18751907
}
18761908

1877-
async securityEvents(sessionToken: hexstring, headers?: Headers) {
1909+
async securityEvents(sessionToken: hexstring, headers?: Headers): Promise<SecurityEvent[]> {
18781910
return this.sessionGet('/securityEvents', sessionToken, headers);
18791911
}
18801912

1881-
async attachedClients(sessionToken: hexstring, headers?: Headers) {
1913+
async attachedClients(sessionToken: hexstring, headers?: Headers): Promise<AttachedClient[]> {
18821914
return this.sessionGet('/account/attached_clients', sessionToken, headers);
18831915
}
18841916

@@ -2636,7 +2668,7 @@ export default class AuthClient {
26362668
accountResetToken: hexstring,
26372669
recoveryKeyId: string,
26382670
headers?: Headers
2639-
) {
2671+
): Promise<RecoveryKeyData> {
26402672
return this.hawkRequest(
26412673
'GET',
26422674
`/recoveryKey/${recoveryKeyId}`,

packages/fxa-content-server/server/lib/beta-settings.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ const settingsConfig = {
6161
serverName: config.get('sentry.serverName'),
6262
},
6363
servers: {
64-
gql: {
65-
url: config.get('settings_gql_url'),
66-
},
6764
oauth: {
6865
url: config.get('fxaccount_url'),
6966
},
@@ -76,6 +73,9 @@ const settingsConfig = {
7673
paymentsNext: {
7774
url: config.get('payments_next_hosted_url'),
7875
},
76+
legalDocs: {
77+
url: config.get('legal_docs_url'),
78+
},
7979
},
8080
oauth: {
8181
clientId: config.get('oauth_client_id'),
@@ -173,10 +173,7 @@ function preconnect(val) {
173173

174174
function resolvePreConnectDirectives(settingsConfig) {
175175
// Using '?' will breaks l10n extraction :9
176-
let gqlUrl, authUrl, oauthUrl, sentryUrl;
177-
try {
178-
gqlUrl = settingsConfig.servers.gql.url;
179-
} catch (e) {}
176+
let authUrl, oauthUrl, sentryUrl;
180177
try {
181178
authUrl = settingsConfig.servers.auth.url;
182179
} catch (e) {}
@@ -188,7 +185,6 @@ function resolvePreConnectDirectives(settingsConfig) {
188185
} catch (e) {}
189186

190187
return {
191-
__GQL_URL_PRECONNECT__: preconnect(gqlUrl),
192188
__AUTH_URL_PRECONNECT__: preconnect(authUrl),
193189
__OAUTH_URL_PRECONNECT__: preconnect(oauthUrl),
194190
__SENTRY_URL_PRECONNECT__: preconnect(sentryUrl),
@@ -249,9 +245,9 @@ const createSettingsProxy = createProxyMiddleware({
249245
// Modify the static settings page by replacing __SERVER_CONFIG__ with the config object
250246
const modifySettingsStatic = function (req, res) {
251247
if (
252-
process.env.NODE_ENV === 'development' &&
248+
['development', 'test'].includes(process.env.NODE_ENV) &&
253249
req.path.startsWith('/settings/') &&
254-
['.js', '.css', '.ftl', '.json', '.svg'].includes(extname(req.path))
250+
['.js', '.css', '.ftl', '.json', '.svg', '.md'].includes(extname(req.path))
255251
) {
256252
const filePath = join(
257253
settingsStaticPath,

0 commit comments

Comments
 (0)