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
30 changes: 30 additions & 0 deletions automation/pages/SettingsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class SettingsPage extends BasePage {
importButtonSelector = 'button-restore-dropdown';
ed25519ImportLinkSelector = 'link-import-ed25519-key';
ecdsaImportLinkSelector = 'link-import-ecdsa-key';
encryptedKeyImportLinkSelector = 'link-import-encrypted-key';
ed25519ImportButtonSelector = 'button-ed25519-private-key-import';
ecdsaImportButtonSelector = 'button-ecdsa-private-key-import';
decryptMainPrivateKeyButtonSelector = 'span-show-modal-0';
Expand All @@ -77,6 +78,11 @@ export class SettingsPage extends BasePage {
importPublicKeyDropdownButtonSelector = 'button-import-public-dropdown';
importSinglePublicKeyButtonSelector = 'import-single-public-key';
importPublicKeyButtonSelector = 'button-public-key-import';
browseEncryptedKeysButtonSelector = 'button-browse-encrypted-keys';
importEncryptedKeysButtonSelector = 'button-import-encrypted-keys';
decryptKeysPasswordInputSelector = 'input-decrypt-keys-password';
decryptEncryptedKeysButtonSelector = 'button-decrypt-encrypted-keys';
skipImportRecoveryPhraseButtonSelector = 'button-skip-import-recovery-phrase';
deletePublicKeyMappingButtonSelector = 'button-delete-public-key-mapping';
selectAllPublicKeysCheckboxSelector = 'checkbox-select-all-public-keys';
deleteAllPublicKeysButtonSelector = 'button-delete-public-all';
Expand Down Expand Up @@ -431,6 +437,30 @@ export class SettingsPage extends BasePage {
await this.click(this.importButtonSelector);
}

async clickOnEncryptedKeysDropdown(): Promise<void> {
await this.click(this.encryptedKeyImportLinkSelector);
}

async clickOnBrowseEncryptedKeyButton(): Promise<void> {
await this.click(this.browseEncryptedKeysButtonSelector);
}

async clickOnImportEncryptedKeyButton(): Promise<void> {
await this.click(this.importEncryptedKeysButtonSelector);
}

async fillInDecryptKeysPassword(password: string): Promise<void> {
await this.fill(this.decryptKeysPasswordInputSelector, password);
}

async clickOnDecryptEncryptedKeyButton(): Promise<void> {
await this.click(this.decryptEncryptedKeysButtonSelector);
}

async clickOnSkipImportRecoveryPhraseButton(): Promise<void> {
await this.click(this.skipImportRecoveryPhraseButtonSelector);
}

async clickOnECDSADropDown(): Promise<void> {
await this.click(this.ecdsaImportLinkSelector);
}
Expand Down
3 changes: 3 additions & 0 deletions automation/tests/helpers/fixtures/settingsKeysSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export function setupSettingsKeysSuite() {
});

return {
get window() {
return window;
},
get credentials() {
return credentials;
},
Expand Down
55 changes: 55 additions & 0 deletions automation/tests/local-basic/settingsKeysImportTests.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { expect, test } from '@playwright/test';
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { generateECDSAKeyPair, generateEd25519KeyPair } from '../../utils/crypto/keyUtil.js';
import { setupSettingsKeysSuite } from '../helpers/fixtures/settingsKeysSuite.js';
import { fileURLToPath } from 'node:url';
import { clearDialogMockState, setDialogMockState } from '../../utils/runtime/dialogMocks.js';

test.describe('Settings keys import tests @local-basic', () => {
const suite = setupSettingsKeysSuite();
Expand Down Expand Up @@ -53,6 +58,56 @@ test.describe('Settings keys import tests @local-basic', () => {
expect(publicKey).toBeTruthy();
});

test('Verify user can import an encrypted private key', async () => {
// Generate private key and encrypt it with a password
const password = 'encrypted-key-password';
const privateKey = crypto
.createPrivateKey({
key: Buffer.from(generateEd25519KeyPair().privateKey, 'hex'),
type: 'pkcs8',
format: 'der',
})
.export({ type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: password });

// Ensure the data directory exists
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dataDirectory = path.resolve(__dirname, '../../data');
if (!fs.existsSync(dataDirectory)) {
fs.mkdirSync(dataDirectory, { recursive: true });
}

// Write the file
const fileName = `encrypted-import-${Date.now()}.pem`;
const filePath = path.resolve(dataDirectory, fileName);
fs.writeFileSync(filePath, privateKey);

console.log(`Encrypted private key file generated at: ${filePath}`);

try {
await suite.settingsPage.clickOnKeysTab();
await suite.settingsPage.clickOnImportButton();
await suite.settingsPage.clickOnEncryptedKeysDropdown();

// Browse opens Electron's native dialog through IPC, not a browser file chooser.
await setDialogMockState(suite.window, { openPaths: [filePath] });
await suite.settingsPage.clickOnBrowseEncryptedKeyButton();
await suite.settingsPage.clickOnImportEncryptedKeyButton();

await suite.settingsPage.clickOnSkipImportRecoveryPhraseButton();
await suite.settingsPage.fillInDecryptKeysPassword(password);
await suite.settingsPage.clickOnDecryptEncryptedKeyButton();

await expect.poll(() => suite.settingsPage.getKeyRowCount()).toBe(2);
await expect
.poll(() => suite.registrationPage.getToastMessage())
.toBe('Keys imported successfully');
} finally {
await clearDialogMockState(suite.window);
fs.rmSync(filePath, { force: true });
}
});

test('Verify user can filter keys by All, Recovery Phrase, and Private Key', async () => {
await suite.settingsPage.clickOnKeysTab();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ watch(
:disabled="decryptPassword.trim().length === 0 || decrypting"
:loading="decrypting"
loading-text="Decrypting..."
data-testid="button-decrypt-encrypted-keys"
>Decrypt</AppButton
>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ watch(
:disable-on-loading="false"
loading-text="Abort Search"
@click="handleSelect"
data-testid="button-encrypted-keys-folder-import"
data-testid="button-browse-encrypted-keys"
>Browse</AppButton
>
<AppButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ const handleClearWords = (value: boolean) => {
>Clear</AppButton
>
<div class="flex-between-centered gap-4">
<AppButton color="secondary" type="button" @click="handleSkip">Skip</AppButton>
<AppButton
color="secondary"
type="button"
@click="handleSkip"
data-testid="button-skip-import-recovery-phrase"
>Skip</AppButton
>
<AppButton color="primary" type="submit" :disabled="!user.recoveryPhrase"
>Import</AppButton
>
Expand Down
Loading