Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions packages/functional-tests/tests/settings/changeEmail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ test.describe('severity-1 #smoke', () => {
initialPassword,
newPassword,
target,
credentials.email
newEmail
);

credentials.password = newPassword;
Expand Down Expand Up @@ -119,7 +119,7 @@ test.describe('severity-1 #smoke', () => {
initialPassword,
newPassword,
target,
credentials.email
secondEmail
);

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

// Change back the primary email again
// Clear stale MFA OTP codes from the password change step
await target.emailClient.clear(secondEmail);
Comment thread
dschom marked this conversation as resolved.

// makePrimaryButton is wrapped in MfaGuard(scope="email").
// After sign-out + sign-in, the JWT cache is cleared, so the MFA modal appears.
await settings.secondaryEmail.makePrimaryButton.click();
await settings.confirmMfaGuard(credentials.email);
await settings.confirmMfaGuard(secondEmail);
await expect(settings.alertBar).toHaveText(
new RegExp(`${initialEmail}.*is now your primary email`)
);
await settings.signOut();

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

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

console.log('credentials.password', credentials.password);
// Update which password to use the account cleanup
// Update which password to use for account cleanup
credentials.password = newPassword;
});

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

await expect(page.getByText('Account deleted successfully')).toBeVisible();

// Try creating a new account with the same secondary email as previous account and new password
await signup.fillOutEmailForm(newEmail);
await signup.fillOutSignupForm(newPassword);
Expand Down Expand Up @@ -290,6 +298,9 @@ async function setNewPassword(
): Promise<void> {
await settings.password.changeButton.click();

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

await changePassword.fillOutChangePassword(oldPassword, newPassword);
Expand Down
164 changes: 0 additions & 164 deletions packages/functional-tests/tests/settings/multitab.spec.ts

This file was deleted.

20 changes: 19 additions & 1 deletion packages/functional-tests/tests/syncV3/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ test.describe('severity-2 #smoke', () => {
test('sign in, delete the account', async ({
target,
syncBrowserPages: {
connectAnotherDevice,
settings,
deleteAccount,
page,
Expand All @@ -147,22 +148,39 @@ test.describe('severity-2 #smoke', () => {
testAccountTracker,
}) => {
const credentials = await testAccountTracker.signUpSync();
const customEventDetail: LinkAccountResponse = {
id: 'account_updates',
message: {
command: FirefoxCommand.LinkAccount,
data: {
ok: true,
},
},
};

await page.goto(
`${target.contentServerUrl}?context=fx_desktop_v3&service=sync&action=email`
);
await signin.respondToWebChannelMessage(customEventDetail);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to do this? It seems like in a fucntional tests stuff like this shouldn't be needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this was surprising to me. Wit the graphql removal, these started to fail, however they were not structured the same way as the other tests which did do this. Its possible that our graphql approach was masking something.

Copy link
Copy Markdown
Contributor

@dschom dschom Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we manually tested this flow? A user won't be able to trigger that respondToWebChannelMessage call, which makes me question if this actually works or we lost something important in the migration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested this locally and the signin/up works as expected, no hanging.

await signin.fillOutEmailFirstForm(credentials.email);
await signin.fillOutPasswordForm(credentials.password);

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

await signin.checkWebChannelMessage(FirefoxCommand.LinkAccount);

const code = await target.emailClient.getVerifyLoginCode(
credentials.email
);
await signinTokenCode.fillOutCodeForm(code);
await expect(page).toHaveURL(/pair/);

await signin.checkWebChannelMessage(FirefoxCommand.Login);

await expect(connectAnotherDevice.fxaConnected).toBeEnabled();

await settings.goto();
await page.waitForURL(/settings/);
await expect(settings.settingsHeading).toBeVisible();
//Click Delete account
await settings.deleteAccountButton.click();
await deleteAccount.deleteAccount(credentials.password);
Expand Down
38 changes: 35 additions & 3 deletions packages/fxa-auth-client/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,38 @@ export type CredentialStatus = {
clientSalt?: string;
};

export interface SecurityEvent {
name: string;
createdAt: number;
verified?: boolean;
}

export interface AttachedClient {
clientId: string;
isCurrentSession: boolean;
userAgent: string;
deviceType: string | null;
deviceId: string | null;
name: string | null;
lastAccessTime: number;
lastAccessTimeFormatted: string;
approximateLastAccessTime: number | null;
approximateLastAccessTimeFormatted: string | null;
location?: {
city?: string | null;
country?: string | null;
state?: string | null;
stateCode?: string | null;
};
os: string | null;
sessionTokenId: string | null;
refreshTokenId: string | null;
}

export interface RecoveryKeyData {
recoveryData: string;
}

export type SignUpOptions = {
keys?: boolean;
service?: string;
Expand Down Expand Up @@ -1874,11 +1906,11 @@ export default class AuthClient {
return this.sessionGet('/account/sessions', sessionToken, headers);
}

async securityEvents(sessionToken: hexstring, headers?: Headers) {
async securityEvents(sessionToken: hexstring, headers?: Headers): Promise<SecurityEvent[]> {
return this.sessionGet('/securityEvents', sessionToken, headers);
}

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

Expand Down Expand Up @@ -2636,7 +2668,7 @@ export default class AuthClient {
accountResetToken: hexstring,
recoveryKeyId: string,
headers?: Headers
) {
): Promise<RecoveryKeyData> {
return this.hawkRequest(
'GET',
`/recoveryKey/${recoveryKeyId}`,
Expand Down
16 changes: 6 additions & 10 deletions packages/fxa-content-server/server/lib/beta-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ const settingsConfig = {
serverName: config.get('sentry.serverName'),
},
servers: {
gql: {
url: config.get('settings_gql_url'),
},
oauth: {
url: config.get('fxaccount_url'),
},
Expand All @@ -76,6 +73,9 @@ const settingsConfig = {
paymentsNext: {
url: config.get('payments_next_hosted_url'),
},
legalDocs: {
url: config.get('legal_docs_url'),
},
},
oauth: {
clientId: config.get('oauth_client_id'),
Expand Down Expand Up @@ -173,10 +173,7 @@ function preconnect(val) {

function resolvePreConnectDirectives(settingsConfig) {
// Using '?' will breaks l10n extraction :9
let gqlUrl, authUrl, oauthUrl, sentryUrl;
try {
gqlUrl = settingsConfig.servers.gql.url;
} catch (e) {}
let authUrl, oauthUrl, sentryUrl;
try {
authUrl = settingsConfig.servers.auth.url;
} catch (e) {}
Expand All @@ -188,7 +185,6 @@ function resolvePreConnectDirectives(settingsConfig) {
} catch (e) {}

return {
__GQL_URL_PRECONNECT__: preconnect(gqlUrl),
__AUTH_URL_PRECONNECT__: preconnect(authUrl),
__OAUTH_URL_PRECONNECT__: preconnect(oauthUrl),
__SENTRY_URL_PRECONNECT__: preconnect(sentryUrl),
Expand Down Expand Up @@ -249,9 +245,9 @@ const createSettingsProxy = createProxyMiddleware({
// Modify the static settings page by replacing __SERVER_CONFIG__ with the config object
const modifySettingsStatic = function (req, res) {
if (
process.env.NODE_ENV === 'development' &&
['development', 'test'].includes(process.env.NODE_ENV) &&
req.path.startsWith('/settings/') &&
['.js', '.css', '.ftl', '.json', '.svg'].includes(extname(req.path))
['.js', '.css', '.ftl', '.json', '.svg', '.md'].includes(extname(req.path))
) {
const filePath = join(
settingsStaticPath,
Expand Down
Loading
Loading