Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(keys): Update key stretching to optionally use session token on password change #18317

Merged
merged 1 commit into from
Feb 11, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ test.describe('severity-2 #smoke', () => {
password: string
) {
const client = target.createAuthClient(version);
const response = await client.signIn(email, password, { keys: true });
const response = await client.signIn(email, password, {
keys: true,
skipPasswordUpgrade: true,
});
expect(response.keyFetchToken).toBeDefined();
expect(response.unwrapBKey).toBeDefined();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ test.describe('severity-2 #smoke', () => {
password: string
) {
const client = target.createAuthClient(version);
const response = await client.signIn(email, password, { keys: true });
const response = await client.signIn(email, password, {
keys: true,
skipPasswordUpgrade: true,
});
expect(response.keyFetchToken).toBeDefined();
expect(response.unwrapBKey).toBeDefined();

const keys = client.accountKeys(
const keys = await client.accountKeys(
response.keyFetchToken as string,
response.unwrapBKey as string
);
Expand Down Expand Up @@ -62,18 +65,25 @@ test.describe('severity-2 #smoke', () => {
recoveryKey,
resetPassword,
confirm#Code,
deleteAccount,
},
testAccountTracker,
}) => {
const { email, password } = testAccountTracker.generateAccountDetails();
const accountDetails = {
email: testAccountTracker.generateEmail(),
password: testAccountTracker.generatePassword(),
};
const newPassword = testAccountTracker.generatePassword();
await page.goto(
`${target.contentServerUrl}/?forceExperiment=generalizedReactApp&forceExperimentGroup=react&${#Version.query}`
);
await page.waitForURL(/\//);
await #.fillOutEmailForm(email);
await #.fillOut#Form(password, AGE_21);
await #.fillOutEmailForm(accountDetails.email);
await #.fillOut#Form(accountDetails.password, AGE_21);
await expect(page).toHaveURL(/confirm_#_code/);
const code = await target.emailClient.getVerifyShortCode(email);
const code = await target.emailClient.getVerifyShortCode(
accountDetails.email
);
await confirm#Code.fillOutCodeForm(code);

await page.waitForURL(/settings/);
Expand All @@ -84,57 +94,72 @@ test.describe('severity-2 #smoke', () => {
const keys = await _getKeys(
#Version.version,
target,
email,
password
accountDetails.email,
accountDetails.password
);
await page.goto(
`${target.contentServerUrl}/?forceExperiment=generalizedReactApp&forceExperimentGroup=react&${resetVersion.query}`
);
await page.waitForURL(/\//);
await signin.fillOutEmailFirstForm(email);
await signin.fillOutPasswordForm(password);
await signin.fillOutEmailFirstForm(accountDetails.email);
await signin.fillOutPasswordForm(accountDetails.password);
await page.waitForURL(/settings/);
await expect(page).toHaveURL(/settings/);

await settings.goto(`${resetVersion.version}`);
await settings.goto(`${resetVersion.query}`);
await page.waitForURL(/settings/);
await settings.recoveryKey.createButton.click();
const key = await recoveryKey.createRecoveryKey(password, HINT);
const key = await recoveryKey.createRecoveryKey(
accountDetails.password,
HINT
);
await settings.signOut();
await page.goto(
`${target.contentServerUrl}/reset_password?${resetVersion.query}`
);
await page.waitForURL(/reset_password/);
await resetPassword.fillOutEmailForm(email);
const resetCode = await target.emailClient.getResetPasswordCode(email);
await resetPassword.fillOutEmailForm(accountDetails.email);
const resetCode = await target.emailClient.getResetPasswordCode(
accountDetails.email
);
await resetPassword.fillOutResetPasswordCodeForm(resetCode);
await resetPassword.fillOutRecoveryKeyForm(key);

await expect(page).toHaveURL(
new RegExp(`account_recovery_reset_password.*${resetVersion.query}`)
);

await resetPassword.fillOutNewPasswordForm(password);
await resetPassword.fillOutNewPasswordForm(newPassword);
accountDetails.password = newPassword;

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

await resetPassword.continueWithoutDownloadingRecoveryKey();
await resetPassword.recoveryKeyFinishButton.click();

// a successful password reset means that the user is signed in
await page.waitForURL(/settings/);
await settings.signOut();

// Attempt to signin
await page.goto(
`${target.contentServerUrl}/?forceExperiment=generalizedReactApp&forceExperimentGroup=react&${signinVersion.query}`
);
await page.waitForURL(/\//);
// a successful password reset means that the user is signed in
await expect(signin.cachedSigninHeading).toBeVisible();
await signin.signInButton.click();

await signin.fillOutEmailFirstForm(accountDetails.email);
await signin.fillOutPasswordForm(accountDetails.password);
await page.waitForURL(/settings/);
await expect(page).toHaveURL(/settings/);

const keys2 = await _getKeys(
signinVersion.version,
target,
email,
password
accountDetails.email,
accountDetails.password
);
expect(keys2.kB).toEqual(keys.kB);

await settings.deleteAccountButton.click();
await deleteAccount.deleteAccount(accountDetails.password);
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ test.describe('severity-2 #smoke', () => {
password: string
) {
const client = target.createAuthClient(version);
const response = await client.signIn(email, password, { keys: true });
const response = await client.signIn(email, password, {
keys: true,
skipPasswordUpgrade: true,
});
expect(response.keyFetchToken).toBeDefined();
expect(response.unwrapBKey).toBeDefined();

Expand Down
49 changes: 35 additions & 14 deletions packages/functional-tests/tests/misc/authClientV2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ test.describe('auth-client-tests', () => {
const { email, password } = testAccountTracker.generateAccountDetails();

await #(client, email, password);

const signInResult = await client.signIn(email, password, { keys: true });
expect(signInResult.keyFetchToken).toBeDefined();
expect(signInResult.unwrapBKey).toBeDefined();
Expand All @@ -129,6 +130,7 @@ test.describe('auth-client-tests', () => {
signInResult.keyFetchToken as string,
signInResult.unwrapBKey as string
);

expect(keys1).toBeDefined();
expect(keys1.kA).toBeDefined();
expect(keys1.kB).toBeDefined();
Expand All @@ -143,13 +145,26 @@ test.describe('auth-client-tests', () => {
expect(statusBefore.clientSalt).toBeUndefined();
expect(statusBefore.currentVersion).toBe('v1');

// The # should automatically reset the password
// The # should automatically upgrade the password, but return V1 creds
const signInResult2 = await client2.signIn(email, password, {
keys: true,
});
expect(signInResult2).toBeDefined();
expect(signInResult2.keyFetchToken).toBeDefined();
expect(signInResult2.unwrapBKey).toBeDefined();
expect(signInResult2.unwrapBKey).toEqual(signInResult.unwrapBKey);

// Grab keys, so we can compare kA and kB
const keys2 = await client.accountKeys(
signInResult2.keyFetchToken as string,
signInResult2.unwrapBKey as string
);

expect(keys2).toBeDefined();
expect(keys2.kA).toBeDefined();
expect(keys2.kB).toBeDefined();
expect(keys2.kA).toEqual(keys1.kA);
expect(keys2.kB).toEqual(keys1.kB);

// Check the status after the signin
const statusAfter = await client2.getCredentialStatusV2(email);
Expand All @@ -159,7 +174,12 @@ test.describe('auth-client-tests', () => {
expect(statusAfter.clientSalt).toMatch('quickStretchV2:');
expect(statusAfter.currentVersion).toBe('v2');

// Check unwrapKB. It should match our V2 credential unwrapBKey.
// Do another signin to get V2 credentials
const signInResult3 = await client2.signIn(email, password, {
keys: true,
});

// Check unwrapKB. It should match our V1 credential unwrapBKey.
const status = await client.getCredentialStatusV2(email);
expect(status.clientSalt).toBeDefined();
expect(status.clientSalt).toBeDefined();
Expand All @@ -168,22 +188,23 @@ test.describe('auth-client-tests', () => {
password,
clientSalt: status.clientSalt as string,
});
expect(credentialsV2.unwrapBKey).toEqual(signInResult2.unwrapBKey);
expect(credentialsV2.unwrapBKey).toEqual(signInResult3.unwrapBKey);

const credentialsV1 = await getCredentials(email, password);
expect(credentialsV1.unwrapBKey).not.toEqual(signInResult2.unwrapBKey);
expect(signInResult2.keyFetchToken).toBeDefined();
expect(signInResult2.unwrapBKey).toBeDefined();
expect(credentialsV1.unwrapBKey).not.toEqual(signInResult3.unwrapBKey);
expect(signInResult3.keyFetchToken).toBeDefined();
expect(signInResult3.unwrapBKey).toBeDefined();

// Check that keys didn't drift
const keys2 = await client2.accountKeys(
signInResult2.keyFetchToken as string,
signInResult2.unwrapBKey as string
const keys3 = await client2.accountKeys(
signInResult3.keyFetchToken as string,
signInResult3.unwrapBKey as string
);
expect(keys2).toBeDefined();
expect(keys2.kA).toBeDefined();
expect(keys2.kB).toBeDefined();
expect(keys2.kA).toEqual(keys1.kA);
expect(keys2.kB).toEqual(keys1.kB);

expect(keys3).toBeDefined();
expect(keys3.kA).toBeDefined();
expect(keys3.kB).toBeDefined();
expect(keys3.kA).toEqual(keys1.kA);
expect(keys3.kB).toEqual(keys1.kB);
});
});
Loading