Merge pull request #16517 from mozilla/fxa-8467

fix(delete): Update delete account route
This commit is contained in:
Vijay Budhram 2024-03-12 15:40:43 -04:00 коммит произвёл GitHub
Родитель 21bc82e14a 4f98baa026
Коммит 91020fa9f4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
26 изменённых файлов: 307 добавлений и 223 удалений

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

@ -1,5 +1,5 @@
import { Browser, test as base, expect, firefox } from '@playwright/test';
import { TargetName, ServerTarget, create, Credentials } from '../targets';
import { Browser, expect, firefox, test as base } from '@playwright/test';
import { create, Credentials, ServerTarget, TargetName } from '../targets';
import { EmailClient } from '../email';
import { create as createPages } from '../../pages';
import { BaseTarget } from '../targets/base';
@ -36,45 +36,60 @@ export const test = base.extend<TestOptions, WorkerOptions>({
const password = 'passwordzxcv';
await target.email.clear(email);
let credentials: Credentials;
try {
credentials = await target.createAccount(email, password);
} catch (e) {
await target.auth.accountDestroy(email, password);
const newCreds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
newCreds.sessionToken
);
credentials = await target.createAccount(email, password);
}
await use(credentials);
//teardown
await target.email.clear(credentials.email);
await target.email.clear(email);
try {
await target.auth.accountDestroy(credentials.email, credentials.password);
} catch (error: any) {
if (error.message === 'Unconfirmed session') {
// If totp was enabled we'll need a verified session to destroy the account
if (credentials.secret) {
// we don't know if the original session still exists
// the test may have called signOut()
const { sessionToken } = await target.auth.signIn(
credentials.email,
credentials.password
);
credentials.sessionToken = sessionToken;
await target.auth.verifyTotpCode(
sessionToken,
await getCode(credentials.secret)
);
await target.auth.accountDestroy(
credentials.email,
credentials.password,
{},
sessionToken
);
} else {
throw error;
}
} else if (error.message !== 'Unknown account') {
throw error;
// we don't know if the original session still exists
// the test may have called signOut()
const { sessionToken } = await target.auth.signIn(
email,
credentials.password
);
if (credentials.secret) {
credentials.sessionToken = sessionToken;
await target.auth.verifyTotpCode(
sessionToken,
await getCode(credentials.secret)
);
}
await target.auth.accountDestroy(
email,
credentials.password,
{},
sessionToken
);
} catch (err) {
if (
err.message ===
'Sign in with this email type is not currently supported'
) {
// The user changed their primary email, the test case must manually destroy the account
} else if (
err.message === 'The request was blocked for security reasons'
) {
// Some accounts are always prompted to unblock their account, ie emails starting
// `blocked.`. These accounts need to be destroyed in the test case
} else if (err.message !== 'Unknown account') {
throw err;
}
//s'ok
}

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

@ -116,6 +116,9 @@ export abstract class BaseLayout {
* index page has been converted to React and our event handling moved.
*/
async sendWebChannelMessage(customEventDetail: CustomEventDetail) {
// Using waitForTimeout is naturally flaky, I'm not sure of other options
// to ensure that browser has had time send all web channel messages.
await this.page.waitForTimeout(2000);
await this.page.evaluate(
({ customEventDetail }) => {
window.dispatchEvent(

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

@ -10,7 +10,7 @@ const CI = !!process.env.CI;
// see .vscode/launch.json
const DEBUG = !!process.env.DEBUG;
const SLOWMO = parseInt(process.env.PLAYWRIGHT_SLOWMO || '0');
const NUM_WORKERS = parseInt(process.env.PLAYWRIGHT_WORKERS || '2');
const NUM_WORKERS = parseInt(process.env.PLAYWRIGHT_WORKERS || '16');
let retries = 0,
workers = NUM_WORKERS || 2,

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

@ -21,9 +21,10 @@ function getClient(url: string, version: SaltVersion) {
test.describe('auth-client-tests', () => {
let email = '';
let password = '';
let credentials: any;
async function signUp(client: AuthClient) {
const credentials = await client.signUp(email, password, {
credentials = await client.signUp(email, password, {
keys: true,
preVerified: 'true',
});
@ -49,7 +50,8 @@ test.describe('auth-client-tests', () => {
});
test.afterEach(async ({ target }) => {
await curClient?.accountDestroy(email, password);
const newCreds = await curClient?.signIn(email, password, { keys: true });
await curClient?.accountDestroy(email, password, {}, newCreds.sessionToken);
});
test('it creates with v1 and signs in', async ({ target }) => {

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

@ -23,7 +23,13 @@ test.describe('severity-1 #smoke', () => {
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, password);
const credentials = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
credentials.sessionToken
);
}
});

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

@ -90,7 +90,7 @@ test.describe('severity-1 #smoke', () => {
target,
pages: { relier, login },
}) => {
await target.auth.signUp(email, password, {
const creds = await target.auth.signUp(email, password, {
lang: 'en',
preVerified: 'true',
});
@ -101,7 +101,7 @@ test.describe('severity-1 #smoke', () => {
//Verify logged in on Settings page
expect(await login.isUserLoggedIn()).toBe(true);
await target.auth.accountDestroy(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
const query = new URLSearchParams({
login_hint: email,
@ -160,10 +160,9 @@ test.describe('severity-1 #smoke', () => {
});
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, password);
}
// Cleanup any accounts created during the test
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
});
test('fails if login_hint is different to logged in user', async ({

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

@ -27,9 +27,17 @@ test.describe('severity-1 #smoke', () => {
});
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, password);
// Cleanup any accounts created during the test
try {
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
creds.sessionToken
);
} catch (e) {
// ignore
}
});

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

@ -19,6 +19,8 @@ test.describe('severity-2 #smoke', () => {
/* Password for fake account */
const password = 'passwordzxcv';
let emailUserCreds;
/* eslint-disable camelcase */
const queryParameters = {
client_id: '7f368c6886429f19',
@ -37,17 +39,20 @@ test.describe('severity-2 #smoke', () => {
test.beforeEach(async ({ target }, { project }) => {
// The `sync` prefix is needed to force confirmation.
email = `sync${Math.random()}@restmail.net`;
await target.createAccount(email, password);
emailUserCreds = await target.createAccount(email, password);
});
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
try {
await target.auth.accountDestroy(email, password);
} catch (e) {
// ignore
}
// Cleanup any accounts created during the test
try {
await target.auth.accountDestroy(
email,
password,
{},
emailUserCreds.sessionToken
);
} catch (e) {
// ignore
}
});

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

@ -16,11 +16,13 @@ test.describe('severity-1 #smoke', () => {
test.describe('signin with OAuth after Sync', () => {
test.afterEach(async ({ target }) => {
if (email) {
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
email = '';
}
if (email2) {
await target.auth.accountDestroy(email2, password);
const creds = await target.auth.signIn(email2, password);
await target.auth.accountDestroy(email2, password, {}, creds.sessionToken);
email2 = '';
}
});
@ -78,7 +80,8 @@ test.describe('severity-1 #smoke', () => {
test.describe('signin to Sync after OAuth', () => {
test.afterEach(async ({ target }) => {
if (email) {
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
email = '';
}
});

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

@ -41,6 +41,7 @@ test.describe('severity-1 #smoke', () => {
await page.getByText('Two-step authentication disabled').waitFor();
const status = await settings.totp.statusText();
expect(status).toEqual('Not Set');
credentials.secret = null;
await relier.goto();
await relier.clickEmailFirst();

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

@ -6,13 +6,14 @@ import { test, expect } from '../../lib/fixtures/standard';
let email;
const password = 'password';
const newPassword = 'new_password';
let emailUserCreds;
test.describe('severity-2 #smoke', () => {
test.describe('post verify - force password change', () => {
test.beforeEach(async ({ target, pages: { login } }) => {
test.slow();
email = login.createEmail('forcepwdchange{id}');
await target.auth.signUp(email, password, {
emailUserCreds = await target.auth.signUp(email, password, {
lang: 'en',
preVerified: 'true',
});
@ -20,9 +21,16 @@ test.describe('severity-2 #smoke', () => {
});
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, newPassword);
// Cleanup any accounts created during the test
try {
await target.auth.accountDestroy(
email,
newPassword,
{},
emailUserCreds.sessionToken
);
} catch (e) {
// ignore
}
});

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

@ -20,7 +20,8 @@ test.describe('severity-2 #smoke', () => {
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, newPassword);
const creds = await target.auth.signIn(email, newPassword);
await target.auth.accountDestroy(email, newPassword, {}, creds.sessionToken);
}
});

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

@ -34,12 +34,15 @@ test.beforeEach(async ({ pages: { configPage, login } }) => {
});
test.afterEach(async ({ target }) => {
if (email) {
// Cleanup any accounts created during the test
const accountStatus = await target.auth.accountStatusByEmail(email);
if (accountStatus.exists) {
await target.auth.accountDestroy(email, PASSWORD);
// Cleanup any accounts created during the test
try {
if (!email) {
return;
}
const creds = await target.auth.signIn(email, PASSWORD);
await target.auth.accountDestroy(email, PASSWORD, {}, creds.sessionToken);
} catch (e) {
// ignore
}
});
@ -110,7 +113,6 @@ test.describe('severity-1 #smoke', () => {
test('signup oauth webchannel - sync mobile or FF desktop 123+', async ({
syncBrowserPages: { page, signupReact, login },
}) => {
test.fixme(true, 'FXA-9096');
const customEventDetail = createCustomEventDetail(
FirefoxCommand.FxAStatus,
{

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

@ -19,6 +19,23 @@ test.describe('severity-1 #smoke', () => {
}
);
test.afterEach(async ({ credentials, target }) => {
try {
const newCreds = await target.auth.signIn(
newEmail,
credentials.password
);
await target.auth.accountDestroy(
newEmail,
credentials.password,
{},
newCreds.sessionToken
);
} catch (err) {
// ignore
}
});
test('change primary email and login', async ({
credentials,
page,
@ -130,14 +147,13 @@ test.describe('severity-1 #smoke', () => {
});
test.describe('change primary - unblock', () => {
let newEmail;
test.beforeEach(
async ({ credentials, pages: { settings, secondaryEmail } }) => {
test.slow();
await settings.goto();
await settings.secondaryEmail.clickAdd();
const newEmail = `blocked${Math.floor(
Math.random() * 100000
)}@restmail.net`;
newEmail = `blocked${Math.floor(Math.random() * 100000)}@restmail.net`;
await secondaryEmail.addAndVerify(newEmail);
await settings.secondaryEmail.clickMakePrimary();
credentials.email = newEmail;
@ -145,6 +161,23 @@ test.describe('severity-1 #smoke', () => {
}
);
test.afterEach(async ({ credentials, target }) => {
try {
const newCreds = await target.auth.signIn(
newEmail,
credentials.password
);
await target.auth.accountDestroy(
newEmail,
credentials.password,
{},
newCreds.sessionToken
);
} catch (err) {
// ignore
}
});
test('change primary email, get blocked with invalid password, redirect enter password page', async ({
credentials,
pages: { login },

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

@ -43,8 +43,10 @@ test.describe('fxa_status web channel message in Settings', () => {
test.afterEach(async ({ target }) => {
if (!skipTest) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(browserEmail, password);
await target.auth.accountDestroy(otherEmail, password);
const credsBrowser = await target.auth.signIn(browserEmail, password);
const credsEmail = await target.auth.signIn(otherEmail, password);
await target.auth.accountDestroy(browserEmail, password, {}, credsBrowser.sessionToken);
await target.auth.accountDestroy(otherEmail, password, {}, credsEmail.sessionToken);
}
});

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

@ -32,6 +32,7 @@ test.describe('severity-1 #smoke', () => {
await page.getByText('Two-step authentication disabled').waitFor();
status = await settings.totp.statusText();
expect(status).toEqual('Not Set');
credentials.secret = null;
// Login to verify no prompt for code
await settings.signOut();

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

@ -19,7 +19,12 @@ test.describe('severity-2 #smoke', () => {
signupReact,
} = await newPagesForSync(target);
const config = await configPage.getConfig();
await target.auth.accountDestroy(credentials.email, credentials.password);
await target.auth.accountDestroy(
credentials.email,
credentials.password,
{},
credentials.sessionToken
);
await page.goto(
`${target.contentServerUrl}?context=fx_desktop_v3&service=sync&action=email`,

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

@ -114,7 +114,7 @@ test.describe('severity-2 #smoke', () => {
);
const email = login.createEmail('sync{id}');
const password = 'password123';
await target.createAccount(email, password);
const creds = await target.createAccount(email, password);
await page.goto(
`${target.contentServerUrl}?context=fx_desktop_v3&service=sync&action=email&`
);
@ -122,7 +122,7 @@ test.describe('severity-2 #smoke', () => {
// Verify the header after login
expect(login.signInCodeHeader()).toBeVisible();
await target.auth.accountDestroy(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
await page.waitForURL(/signin_bounced/);
//Verify sign in bounced header

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

@ -38,7 +38,13 @@ test.describe('severity-2 #smoke', () => {
for (const email of emails) {
if (email) {
try {
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
creds.sessionToken
);
} catch (e) {
// Handle any errors if needed
}

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

@ -27,11 +27,13 @@ test.describe('severity-2 #smoke', () => {
test.slow(); //The cleanup was timing out and exceeding 3000ms
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(email, password, {}, creds.sessionToken);
}
if (email2) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email2, password);
const creds = await target.auth.signIn(email2, password);
await target.auth.accountDestroy(email2, password, {}, creds.sessionToken);
}
});

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

@ -32,7 +32,13 @@ test.describe('severity-2 #smoke', () => {
for (const email of emails) {
if (email) {
// Cleanup any accounts created during the test
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
creds.sessionToken
);
}
}
});

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

@ -46,7 +46,13 @@ test.describe('severity-2 #smoke', () => {
test.afterEach(async ({ target }) => {
if (email) {
await target.auth.accountDestroy(email, secondPassword);
const creds = await target.auth.signIn(email, secondPassword);
await target.auth.accountDestroy(
email,
secondPassword,
{},
creds.sessionToken
);
}
});

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

@ -30,7 +30,13 @@ test.describe('severity-2 #smoke', () => {
for (const email of emails) {
if (email) {
try {
await target.auth.accountDestroy(email, password);
const creds = await target.auth.signIn(email, password);
await target.auth.accountDestroy(
email,
password,
{},
creds.sessionToken
);
} catch (e) {
// Handle any errors if needed
}

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

@ -888,7 +888,7 @@ export default class AuthClient {
options: {
skipCaseError?: boolean;
} = {},
sessionToken?: hexstring
sessionToken: hexstring
): Promise<any> {
const credentials = await crypto.getCredentials(email, password);
const payload = {
@ -896,15 +896,7 @@ export default class AuthClient {
authPW: credentials.authPW,
};
try {
if (sessionToken) {
return await this.sessionPost(
'/account/destroy',
sessionToken,
payload
);
} else {
return await this.request('POST', '/account/destroy', payload);
}
return await this.sessionPost('/account/destroy', sessionToken, payload);
} catch (error: any) {
if (
error &&

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

@ -2367,8 +2367,8 @@ export const accountRoutes = (
options: {
...ACCOUNT_DOCS.ACCOUNT_DESTROY_POST,
auth: {
mode: 'optional',
strategy: 'sessionToken',
payload: 'required',
},
validate: {
payload: isA.object({

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

@ -11,140 +11,112 @@ const Client = require('../client')();
const config = require('../../config').default.getProperties();
// Note, intentionally not indenting for code review.
[{version:""},{version:"V2"}].forEach((testOptions) => {
[{ version: '' }, { version: 'V2' }].forEach((testOptions) => {
describe(`#integration${testOptions.version} - remote account destroy`, function () {
this.timeout(15000);
let server;
describe(`#integration${testOptions.version} - remote account destroy`, function () {
this.timeout(15000);
let server;
before(() => {
return TestServer.start(config).then((s) => {
server = s;
});
});
before(() => {
return TestServer.start(config).then((s) => {
server = s;
it('account destroy', () => {
const email = server.uniqueEmail();
const password = 'allyourbasearebelongtous';
let client = null;
return Client.createAndVerify(
config.publicUrl,
email,
password,
server.mailbox,
testOptions
)
.then((x) => {
client = x;
return client.sessionStatus();
})
.then((status) => {
return client.destroyAccount();
})
.then(() => {
return client.keys();
})
.then(
(keys) => {
assert(false, 'account not destroyed');
},
(err) => {
assert.equal(err.message, 'Unknown account', 'account destroyed');
}
);
});
it('invalid authPW on account destroy', () => {
const email = server.uniqueEmail();
const password = 'ok';
return Client.createAndVerify(
config.publicUrl,
email,
password,
server.mailbox,
testOptions
)
.then((c) => {
c.authPW = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
);
c.authPWVersion2 = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
);
return c.destroyAccount();
})
.then(
(r) => {
assert(false);
},
(err) => {
assert.equal(err.errno, 103);
}
);
});
it('should fail to delete account with TOTP with unverified session', () => {
const email = server.uniqueEmail();
const password = 'ok';
let client;
return Client.createAndVerifyAndTOTP(
config.publicUrl,
email,
password,
server.mailbox,
{
...testOptions,
keys: true,
}
)
.then(() => {
// Create a new unverified session
return Client.login(config.publicUrl, email, password, testOptions);
})
.then((response) => {
client = response;
return client.emailStatus();
})
.then((res) =>
assert.equal(res.sessionVerified, false, 'session not verified')
)
.then(() => client.destroyAccount())
.then(assert.fail, (err) => {
assert.equal(err.errno, 138, 'unverified session');
});
});
after(() => {
return TestServer.stop(server);
});
});
it('account destroy', () => {
const email = server.uniqueEmail();
const password = 'allyourbasearebelongtous';
let client = null;
return Client.createAndVerify(
config.publicUrl,
email,
password,
server.mailbox,
testOptions
)
.then((x) => {
client = x;
return client.sessionStatus();
})
.then((status) => {
return client.destroyAccount();
})
.then(() => {
return client.keys();
})
.then(
(keys) => {
assert(false, 'account not destroyed');
},
(err) => {
assert.equal(err.message, 'Unknown account', 'account destroyed');
}
);
});
it('invalid authPW on account destroy', () => {
const email = server.uniqueEmail();
const password = 'ok';
return Client.createAndVerify(
config.publicUrl,
email,
password,
server.mailbox,
testOptions
)
.then((c) => {
c.authPW = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
);
c.authPWVersion2 = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
);
return c.destroyAccount();
})
.then(
(r) => {
assert(false);
},
(err) => {
assert.equal(err.errno, 103);
}
);
});
it('should fail to delete account with TOTP without passing sessionToken', () => {
const email = server.uniqueEmail();
const password = 'ok';
let client;
return Client.createAndVerifyAndTOTP(
config.publicUrl,
email,
password,
server.mailbox,
{
...testOptions,
keys: true
}
)
.then((res) => {
client = res;
// Doesn't specify a sessionToken to use
delete client.sessionToken;
return client.destroyAccount();
})
.then(assert.fail, (err) => {
assert.equal(err.errno, 138, 'unverified session');
});
});
it('should fail to delete account with TOTP with unverified session', () => {
const email = server.uniqueEmail();
const password = 'ok';
let client;
return Client.createAndVerifyAndTOTP(
config.publicUrl,
email,
password,
server.mailbox,
{
...testOptions,
keys: true
}
)
.then(() => {
// Create a new unverified session
return Client.login(config.publicUrl, email, password, testOptions);
})
.then((response) => {
client = response;
return client.emailStatus();
})
.then((res) =>
assert.equal(res.sessionVerified, false, 'session not verified')
)
.then(() => client.destroyAccount())
.then(assert.fail, (err) => {
assert.equal(err.errno, 138, 'unverified session');
});
});
after(() => {
return TestServer.stop(server);
});
});
});