Skip to content

fix: update alias handling in CipherStorage to include prefix based on cipher and auth requirements #736

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
50444f9
fix: update alias handling in CipherStorageKeystoreAesGcm to include …
Bowlerr Mar 17, 2025
b503f43
Merge remote-tracking branch 'upstream/master' into fix-alias-collision
Bowlerr Mar 17, 2025
b1a34ae
fix: update alias prefix handling in CipherStorageKeystoreAesGcm to r…
Bowlerr Mar 17, 2025
7259a9a
fix: update alias retrieval to use prefixed alias in CipherStorageKey…
Bowlerr Mar 17, 2025
3d1681f
fix: update alias prefix in getPrefixedAlias to use 'auth_' instead o…
Bowlerr Mar 17, 2025
225010c
fix: enhance alias handling in CipherStorageKeystoreAesGcm to support…
Bowlerr Mar 17, 2025
665a98b
refactor: refacor alias prefix handling to StorageBase
Bowlerr Mar 19, 2025
20974ef
Merge commit 'f4842489db72851361097722996ad324625589e0' into fix-alia…
Bowlerr Mar 23, 2025
ec5cb66
Merge commit '39ae46f2e95f719230f89a175d823c33642f2249' into fix-alia…
Bowlerr Mar 23, 2025
6b21d9b
Merge commit '2ff1c8917b7efe53c107c5ee499a7e14e9dc613a' into fix-alia…
Bowlerr Mar 23, 2025
a148ad4
feat: refactor e2e tests to use expectCredentialsSavedMessage helper …
Bowlerr Mar 24, 2025
64a1bc0
fix: await expect in expectCredentialsSavedMessage for proper async h…
Bowlerr Mar 24, 2025
faf7e48
feat: replace matchLoadInfo with expectCredentialsLoadedMessage for i…
Bowlerr Mar 24, 2025
89d7f04
refactor: handle expecting regex per platform
Bowlerr Mar 24, 2025
d1ebb0f
fix: add return statement in expectRegexText
Bowlerr Mar 24, 2025
72d7307
feat: implement ResetDevice helper for consistent app reset in e2e tests
Bowlerr Mar 24, 2025
9073931
refactor: simplify removing platform-based expectRegexTest
Bowlerr Mar 24, 2025
480cfe3
refactor: replace expectRegexText with waitForRegexText and revert de…
Bowlerr Mar 24, 2025
ee97bc9
refactor: implement expectRegexText with retry logic for improved rel…
Bowlerr Mar 24, 2025
b810515
refactor: streamline wait times in authHelpers
Bowlerr Mar 24, 2025
4e6a47b
revert: ResetDevice helper
Bowlerr Mar 24, 2025
65ab402
fix: increase wait times in biometrics and passcode entry for improve…
Bowlerr Mar 24, 2025
09b5e1e
Merge commit '4b4e22cb3d6064c9bd7b897ba63bdba18baca84f' into fix-alia…
Bowlerr Mar 26, 2025
e7e3080
Merge commit '65ab40235296b79037a56ab6d6e61a89b536905e' into fix-alia…
Bowlerr Mar 26, 2025
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
45 changes: 28 additions & 17 deletions KeychainExample/e2e/testCases/accessControlTest.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { by, device, element, expect, waitFor } from 'detox';
import { matchLoadInfo } from '../utils/matchLoadInfo';
import { by, device, element, expect } from 'detox';
import {
waitForAuthValidity,
enterBiometrics,
enterPasscode,
} from '../utils/authHelpers';
import {
expectCredentialsLoadedMessage,
expectCredentialsSavedMessage,
expectCredentialsResetMessage,
} from '../utils/statusMessageHelpers';

describe('Access Control', () => {
beforeEach(async () => {
Expand All @@ -28,22 +32,21 @@ describe('Access Control', () => {
await enterPasscode();
// Hide keyboard if open
await element(by.text('Keychain Example')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(4000);
await expectCredentialsSavedMessage();

await waitForAuthValidity();
await element(by.text('Load')).tap();
await enterPasscode();
// Hide keyboard if open
await element(by.text('Keychain Example')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernamePasscode',
'testPasswordPasscode',
'KeystoreAESGCM'
);
}
);

it(
' should save and retrieve username and password with biometrics - ' +
type,
Expand All @@ -69,15 +72,16 @@ describe('Access Control', () => {
await element(by.text('Save')).tap();
await enterBiometrics();

await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();

await waitForAuthValidity();
await element(by.text('Load')).tap();
await enterBiometrics();

await matchLoadInfo('testUsernameBiometrics', 'testPasswordBiometrics');
await expectCredentialsLoadedMessage(
'testUsernameBiometrics',
'testPasswordBiometrics'
);
}
);

Expand All @@ -91,7 +95,10 @@ describe('Access Control', () => {
).toBeVisible();
await element(by.text('Load')).tap();
await enterBiometrics();
await matchLoadInfo('testUsernameBiometrics', 'testPasswordBiometrics');
await expectCredentialsLoadedMessage(
'testUsernameBiometrics',
'testPasswordBiometrics'
);
}
);

Expand All @@ -112,11 +119,12 @@ describe('Access Control', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo('testUsernameAny', 'testPasswordAny');
await expectCredentialsLoadedMessage(
'testUsernameAny',
'testPasswordAny'
);
}
);

Expand All @@ -129,7 +137,10 @@ describe('Access Control', () => {
element(by.text('hasGenericPassword: true'))
).toBeVisible();
await element(by.text('Load')).tap();
await matchLoadInfo('testUsernameAny', 'testPasswordAny');
await expectCredentialsLoadedMessage(
'testUsernameAny',
'testPasswordAny'
);
}
);
});
Expand All @@ -139,6 +150,6 @@ describe('Access Control', () => {
// Hide keyboard

await element(by.text('Reset')).tap();
await expect(element(by.text(/^Credentials Reset!$/))).toBeVisible();
await expectCredentialsResetMessage();
});
});
28 changes: 13 additions & 15 deletions KeychainExample/e2e/testCases/securityLevelTest.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { by, device, element, expect, waitFor } from 'detox';
import { matchLoadInfo } from '../utils/matchLoadInfo';
import { by, element, expect, device } from 'detox';
import {
expectCredentialsLoadedMessage,
expectCredentialsSavedMessage,
expectCredentialsResetMessage,
} from '../utils/statusMessageHelpers';

describe(':android:Security Level', () => {
beforeEach(async () => {
Expand All @@ -19,11 +23,9 @@ describe(':android:Security Level', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameAny',
'testPasswordAny',
undefined,
Expand All @@ -46,11 +48,9 @@ describe(':android:Security Level', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameSoftware',
'testPasswordSoftware',
undefined,
Expand All @@ -74,11 +74,9 @@ describe(':android:Security Level', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameHardware',
'testPasswordHardware',
undefined,
Expand All @@ -93,6 +91,6 @@ describe(':android:Security Level', () => {
// Hide keyboard

await element(by.text('Reset')).tap();
await expect(element(by.text(/^Credentials Reset!$/))).toBeVisible();
await expectCredentialsResetMessage();
});
});
38 changes: 16 additions & 22 deletions KeychainExample/e2e/testCases/storageTypesTest.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { by, device, element, expect, waitFor } from 'detox';
import { matchLoadInfo } from '../utils/matchLoadInfo';
import { by, element, expect, device } from 'detox';
import { enterBiometrics, waitForAuthValidity } from '../utils/authHelpers';

import {
expectCredentialsLoadedMessage,
expectCredentialsSavedMessage,
expectCredentialsResetMessage,
} from '../utils/statusMessageHelpers';

describe(':android:Storage Types', () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true });
Expand All @@ -20,11 +25,9 @@ describe(':android:Storage Types', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameAESCBC',
'testPasswordAESCBC',
'KeystoreAESCBC',
Expand All @@ -46,13 +49,11 @@ describe(':android:Storage Types', () => {
await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await enterBiometrics();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await waitForAuthValidity();
await element(by.text('Load')).tap();
await enterBiometrics();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameAESGCM',
'testPasswordAESGCM',
'KeystoreAESGCM',
Expand All @@ -79,11 +80,9 @@ describe(':android:Storage Types', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(3000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameAESGCMNoAuth',
'testPasswordAESGCMNoAuth',
'KeystoreAESGCM_NoAuth',
Expand All @@ -105,15 +104,10 @@ describe(':android:Storage Types', () => {

await expect(element(by.text('Save'))).toBeVisible();
await element(by.text('Save')).tap();
await waitFor(element(by.text(/^Credentials saved! .*$/)))
.toExist()
.withTimeout(5000);
await expectCredentialsSavedMessage();
await element(by.text('Load')).tap();
await enterBiometrics();
await waitFor(element(by.text(/^Credentials loaded! .*$/)))
.toExist()
.withTimeout(5000);
await matchLoadInfo(
await expectCredentialsLoadedMessage(
'testUsernameRSA',
'testPasswordRSA',
'KeystoreRSAECB',
Expand All @@ -127,6 +121,6 @@ describe(':android:Storage Types', () => {
// Hide keyboard

await element(by.text('Reset')).tap();
await expect(element(by.text(/^Credentials Reset!$/))).toExist();
await expectCredentialsResetMessage();
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { device } from 'detox';
import cp from 'child_process';
import { device } from 'detox';

// Wait for 5 seconds to ensure auth validity period has expired
export const waitForAuthValidity = async () => {
await new Promise((resolve) => setTimeout(resolve, 5500)); // Added 500ms buffer
await new Promise((resolve) => setTimeout(resolve, 5500)); // buffer needed for auth validity period
};

export const enterBiometrics = async () => {
Expand All @@ -12,16 +12,14 @@ export const enterBiometrics = async () => {
if (device.getPlatform() === 'android') {
await new Promise((resolve) => setTimeout(resolve, 1000));
cp.spawnSync('adb', ['-e', 'emu', 'finger', 'touch', '1']);
await new Promise((resolve) => setTimeout(resolve, 500));
}
};

export const enterPasscode = async () => {
if (device.getPlatform() === 'android') {
await new Promise((resolve) => setTimeout(resolve, 1500));
await new Promise((resolve) => setTimeout(resolve, 1000));
cp.spawnSync('adb', ['shell', 'input', 'text', '1111']);
await new Promise((resolve) => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 1000));
cp.spawnSync('adb', ['shell', 'input', 'keyevent', '66']);
await new Promise((resolve) => setTimeout(resolve, 1500));
}
};
36 changes: 36 additions & 0 deletions KeychainExample/e2e/utils/detoxHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { by, element, waitFor, expect } from 'detox';

async function retry<T>(
operation: () => Promise<T>,
maxAttempts: number = 3,
delayMs: number = 1000
): Promise<T> {
let attempts = 0;

while (attempts < maxAttempts) {
try {
return await operation();
} catch (error) {
attempts++;
if (attempts === maxAttempts) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw new Error('Unreachable code');
}

export async function expectRegexText(regex: RegExp, timeout?: number) {
try {
return await retry(async () =>
timeout
? waitFor(element(by.text(regex)))
.toBeVisible()
.withTimeout(timeout)
: expect(element(by.text(regex))).toBeVisible()
);
} catch (error) {
throw new Error(`Failed to find text matching ${regex}: ${error}`);
}
}
26 changes: 0 additions & 26 deletions KeychainExample/e2e/utils/matchLoadInfo.ts

This file was deleted.

Loading