mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-16227] Move import to sdk and enable it in browser/web (#12479)
* Move import to sdk and enable it in browser/web * Add uncomitted files * Update package lock * Fix prettier formatting * Fix build * Rewrite import logic * Update ssh import logic for cipher form component * Fix build on browser * Break early in retry logic * Fix build * Fix build * Fix build errors * Update paste icons and throw error on wrong import * Fix tests * Fix build for cli * Undo change to jest config * Undo change to feature flag enum * Remove unneeded lifetime * Fix browser build * Refactor control flow * Fix i18n key and improve import behavior * Remove for loop limit * Clean up tests * Remove unused code * Update libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts Co-authored-by: SmithThe4th <gsmith@bitwarden.com> * Move import logic to service and add tests * Fix linting * Remove erroneous includes * Attempt to fix storybook * Fix storybook, explicitly implement ssh-import-prompt service abstraction * Fix eslint * Update libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * Fix services module * Remove ssh import sdk init code * Add tests for errors * Fix import * Fix import * Fix pkcs8 encrypted key not parsing * Fix import button showing on web --------- Co-authored-by: SmithThe4th <gsmith@bitwarden.com> Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com>
This commit is contained in:
@@ -41,7 +41,7 @@ import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { generate_ssh_key } from "@bitwarden/sdk-internal";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
@@ -131,7 +131,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
protected configService: ConfigService,
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
protected toastService: ToastService,
|
||||
private sdkService: SdkService,
|
||||
protected sdkService: SdkService,
|
||||
private sshImportPromptService: SshImportPromptService,
|
||||
) {
|
||||
this.typeOptions = [
|
||||
{ name: i18nService.t("typeLogin"), value: CipherType.Login },
|
||||
@@ -824,6 +825,15 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
||||
return true;
|
||||
}
|
||||
|
||||
async importSshKeyFromClipboard() {
|
||||
const key = await this.sshImportPromptService.importSshKeyFromClipboard();
|
||||
if (key != null) {
|
||||
this.cipher.sshKey.privateKey = key.privateKey;
|
||||
this.cipher.sshKey.publicKey = key.publicKey;
|
||||
this.cipher.sshKey.keyFingerprint = key.keyFingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
private async generateSshKey(showNotification: boolean = true) {
|
||||
await firstValueFrom(this.sdkService.client$);
|
||||
const sshKey = generate_ssh_key("Ed25519");
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"@bitwarden/generator-history": ["../tools/generator/extensions/history/src"],
|
||||
"@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"],
|
||||
"@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"],
|
||||
"@bitwarden/importer/core": ["../importer/src"],
|
||||
"@bitwarden/importer-ui": ["../importer/src/components"],
|
||||
"@bitwarden/key-management": ["../key-management/src"],
|
||||
"@bitwarden/platform": ["../platform/src"],
|
||||
"@bitwarden/ui-common": ["../ui/common/src"],
|
||||
|
||||
@@ -73,6 +73,7 @@ export class CipherExport {
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
view.sshKey = SshKeyExport.toView(req.sshKey);
|
||||
break;
|
||||
}
|
||||
|
||||
if (req.passwordHistory != null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { import_ssh_key } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key";
|
||||
@@ -17,16 +18,18 @@ export class SshKeyExport {
|
||||
}
|
||||
|
||||
static toView(req: SshKeyExport, view = new SshKeyView()) {
|
||||
view.privateKey = req.privateKey;
|
||||
view.publicKey = req.publicKey;
|
||||
view.keyFingerprint = req.keyFingerprint;
|
||||
const parsedKey = import_ssh_key(req.privateKey);
|
||||
view.privateKey = parsedKey.privateKey;
|
||||
view.publicKey = parsedKey.publicKey;
|
||||
view.keyFingerprint = parsedKey.fingerprint;
|
||||
return view;
|
||||
}
|
||||
|
||||
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
|
||||
domain.privateKey = req.privateKey != null ? new EncString(req.privateKey) : null;
|
||||
domain.publicKey = req.publicKey != null ? new EncString(req.publicKey) : null;
|
||||
domain.keyFingerprint = req.keyFingerprint != null ? new EncString(req.keyFingerprint) : null;
|
||||
const parsedKey = import_ssh_key(req.privateKey);
|
||||
domain.privateKey = new EncString(parsedKey.privateKey);
|
||||
domain.publicKey = new EncString(parsedKey.publicKey);
|
||||
domain.keyFingerprint = new EncString(parsedKey.fingerprint);
|
||||
return domain;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// TODO: Remove once billing stops depending on components
|
||||
"@bitwarden/components": ["../components/src"],
|
||||
"@bitwarden/key-management": ["../key-management/src"],
|
||||
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"],
|
||||
"@bitwarden/platform": ["../platform/src"],
|
||||
// TODO: Remove once billing stops depending on components
|
||||
"@bitwarden/ui-common": ["../ui/common/src"]
|
||||
|
||||
@@ -7,7 +7,7 @@ const sharedConfig = require("../shared/jest.config.ts");
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
preset: "ts-jest",
|
||||
preset: "jest-preset-angular",
|
||||
testEnvironment: "jsdom",
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
|
||||
@@ -37,6 +37,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
@@ -96,6 +97,7 @@ const safeProviders: SafeProvider[] = [
|
||||
EncryptService,
|
||||
PinServiceAbstraction,
|
||||
AccountService,
|
||||
SdkService,
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { BitwardenPasswordProtectedImporter } from "../importers/bitwarden/bitwarden-password-protected-importer";
|
||||
import { Importer } from "../importers/importer";
|
||||
@@ -30,6 +33,7 @@ describe("ImportService", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let sdkService: MockProxy<SdkService>;
|
||||
|
||||
beforeEach(() => {
|
||||
cipherService = mock<CipherService>();
|
||||
@@ -40,6 +44,9 @@ describe("ImportService", () => {
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
const mockClient = mock<BitwardenClient>();
|
||||
sdkService = mock<SdkService>();
|
||||
sdkService.client$ = of(mockClient, mockClient, mockClient);
|
||||
|
||||
importService = new ImportService(
|
||||
cipherService,
|
||||
@@ -51,6 +58,7 @@ describe("ImportService", () => {
|
||||
encryptService,
|
||||
pinService,
|
||||
accountService,
|
||||
sdkService,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/reque
|
||||
import { KvpRequest } from "@bitwarden/common/models/request/kvp.request";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
@@ -114,6 +115,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
private encryptService: EncryptService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
private sdkService: SdkService,
|
||||
) {}
|
||||
|
||||
getImportOptions(): ImportOption[] {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { SshKeyData } from "@bitwarden/common/vault/models/data/ssh-key.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
@@ -39,6 +40,8 @@ import {
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests";
|
||||
|
||||
import { SshImportPromptService } from "../services/ssh-import-prompt.service";
|
||||
|
||||
import { CipherFormService } from "./abstractions/cipher-form.service";
|
||||
import { TotpCaptureService } from "./abstractions/totp-capture.service";
|
||||
import { CipherFormModule } from "./cipher-form.module";
|
||||
@@ -146,6 +149,12 @@ export default {
|
||||
enabled$: new BehaviorSubject(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SshImportPromptService,
|
||||
useValue: {
|
||||
importSshKeyFromClipboard: () => Promise.resolve(new SshKeyData()),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherFormGenerationService,
|
||||
useValue: {
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
data-testid="toggle-privateKey-visibility"
|
||||
bitPasswordInputToggle
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-paste"
|
||||
bitSuffix
|
||||
data-testid="import-privateKey"
|
||||
*ngIf="showImport"
|
||||
(click)="importSshKeyFromClipboard()"
|
||||
></button>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
|
||||
@@ -7,7 +7,8 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
} from "@bitwarden/components";
|
||||
import { generate_ssh_key } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SshImportPromptService } from "../../../services/ssh-import-prompt.service";
|
||||
import { CipherFormContainer } from "../../cipher-form-container";
|
||||
|
||||
@Component({
|
||||
@@ -60,11 +62,14 @@ export class SshKeySectionComponent implements OnInit {
|
||||
keyFingerprint: [""],
|
||||
});
|
||||
|
||||
showImport = false;
|
||||
|
||||
constructor(
|
||||
private cipherFormContainer: CipherFormContainer,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
private sdkService: SdkService,
|
||||
private sshImportPromptService: SshImportPromptService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {
|
||||
this.cipherFormContainer.registerChildForm("sshKeyDetails", this.sshKeyForm);
|
||||
this.sshKeyForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
|
||||
@@ -87,6 +92,11 @@ export class SshKeySectionComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.sshKeyForm.disable();
|
||||
|
||||
// Web does not support clipboard access
|
||||
if (this.platformUtilsService.getClientType() !== ClientType.Web) {
|
||||
this.showImport = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Set form initial form values from the current cipher */
|
||||
@@ -100,6 +110,17 @@ export class SshKeySectionComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async importSshKeyFromClipboard() {
|
||||
const key = await this.sshImportPromptService.importSshKeyFromClipboard();
|
||||
if (key != null) {
|
||||
this.sshKeyForm.setValue({
|
||||
privateKey: key.privateKey,
|
||||
publicKey: key.publicKey,
|
||||
keyFingerprint: key.keyFingerprint,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async generateSshKey() {
|
||||
await firstValueFrom(this.sdkService.client$);
|
||||
const sshKey = generate_ssh_key("Ed25519");
|
||||
|
||||
@@ -25,8 +25,10 @@ export * from "./components/add-edit-folder-dialog/add-edit-folder-dialog.compon
|
||||
export * from "./components/carousel";
|
||||
|
||||
export * as VaultIcons from "./icons";
|
||||
|
||||
export * from "./tasks";
|
||||
|
||||
export { DefaultSshImportPromptService } from "./services/default-ssh-import-prompt.service";
|
||||
export { SshImportPromptService } from "./services/ssh-import-prompt.service";
|
||||
|
||||
export * from "./abstractions/change-login-password.service";
|
||||
export * from "./services/default-change-login-password.service";
|
||||
|
||||
109
libs/vault/src/services/default-ssh-import-prompt.service.ts
Normal file
109
libs/vault/src/services/default-ssh-import-prompt.service.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SshKeyApi } from "@bitwarden/common/vault/models/api/ssh-key.api";
|
||||
import { SshKeyData } from "@bitwarden/common/vault/models/data/ssh-key.data";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { SshKeyPasswordPromptComponent } from "@bitwarden/importer-ui";
|
||||
import { import_ssh_key, SshKeyImportError, SshKeyView } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SshImportPromptService } from "./ssh-import-prompt.service";
|
||||
|
||||
/**
|
||||
* Used to import ssh keys and prompt for their password.
|
||||
*/
|
||||
@Injectable()
|
||||
export class DefaultSshImportPromptService implements SshImportPromptService {
|
||||
constructor(
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
async importSshKeyFromClipboard(): Promise<SshKeyData | null> {
|
||||
const key = await this.platformUtilsService.readFromClipboard();
|
||||
|
||||
let isPasswordProtectedSshKey = false;
|
||||
|
||||
let parsedKey: SshKeyView | null = null;
|
||||
|
||||
try {
|
||||
parsedKey = import_ssh_key(key);
|
||||
} catch (e) {
|
||||
const error = e as SshKeyImportError;
|
||||
if (error.variant === "PasswordRequired" || error.variant === "WrongPassword") {
|
||||
isPasswordProtectedSshKey = true;
|
||||
} else {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t(this.sshImportErrorVariantToI18nKey(error.variant)),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPasswordProtectedSshKey) {
|
||||
for (;;) {
|
||||
const password = await this.getSshKeyPassword();
|
||||
if (password === "" || password == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
parsedKey = import_ssh_key(key, password);
|
||||
break;
|
||||
} catch (e) {
|
||||
const error = e as SshKeyImportError;
|
||||
if (error.variant !== "WrongPassword") {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: this.i18nService.t(this.sshImportErrorVariantToI18nKey(error.variant)),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("sshKeyImported"),
|
||||
});
|
||||
|
||||
return new SshKeyData(
|
||||
new SshKeyApi({
|
||||
privateKey: parsedKey!.privateKey,
|
||||
publicKey: parsedKey!.publicKey,
|
||||
keyFingerprint: parsedKey!.fingerprint,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private sshImportErrorVariantToI18nKey(variant: string): string {
|
||||
switch (variant) {
|
||||
case "ParsingError":
|
||||
return "invalidSshKey";
|
||||
case "UnsupportedKeyType":
|
||||
return "sshKeyTypeUnsupported";
|
||||
case "PasswordRequired":
|
||||
case "WrongPassword":
|
||||
return "sshKeyWrongPassword";
|
||||
default:
|
||||
return "errorOccurred";
|
||||
}
|
||||
}
|
||||
|
||||
private async getSshKeyPassword(): Promise<string | undefined> {
|
||||
const dialog = this.dialogService.open<string>(SshKeyPasswordPromptComponent, {
|
||||
ariaModal: true,
|
||||
});
|
||||
|
||||
return await firstValueFrom(dialog.closed);
|
||||
}
|
||||
}
|
||||
111
libs/vault/src/services/ssh-import-prompt.service.spec.ts
Normal file
111
libs/vault/src/services/ssh-import-prompt.service.spec.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SshKeyApi } from "@bitwarden/common/vault/models/api/ssh-key.api";
|
||||
import { SshKeyData } from "@bitwarden/common/vault/models/data/ssh-key.data";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import * as sdkInternal from "@bitwarden/sdk-internal";
|
||||
|
||||
import { DefaultSshImportPromptService } from "./default-ssh-import-prompt.service";
|
||||
|
||||
jest.mock("@bitwarden/sdk-internal");
|
||||
|
||||
const exampleSshKey = {
|
||||
privateKey: "private_key",
|
||||
publicKey: "public_key",
|
||||
fingerprint: "key_fingerprint",
|
||||
} as sdkInternal.SshKeyView;
|
||||
|
||||
const exampleSshKeyData = new SshKeyData(
|
||||
new SshKeyApi({
|
||||
publicKey: exampleSshKey.publicKey,
|
||||
privateKey: exampleSshKey.privateKey,
|
||||
keyFingerprint: exampleSshKey.fingerprint,
|
||||
}),
|
||||
);
|
||||
|
||||
describe("SshImportPromptService", () => {
|
||||
let sshImportPromptService: DefaultSshImportPromptService;
|
||||
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
let toastService: MockProxy<ToastService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
|
||||
beforeEach(() => {
|
||||
dialogService = mock<DialogService>();
|
||||
toastService = mock<ToastService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
i18nService = mock<I18nService>();
|
||||
|
||||
sshImportPromptService = new DefaultSshImportPromptService(
|
||||
dialogService,
|
||||
toastService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("importSshKeyFromClipboard()", () => {
|
||||
it("imports unencrypted ssh key", async () => {
|
||||
jest.spyOn(sdkInternal, "import_ssh_key").mockReturnValue(exampleSshKey);
|
||||
platformUtilsService.readFromClipboard.mockResolvedValue("ssh_key");
|
||||
expect(await sshImportPromptService.importSshKeyFromClipboard()).toEqual(exampleSshKeyData);
|
||||
});
|
||||
|
||||
it("requests password for encrypted ssh key", async () => {
|
||||
jest
|
||||
.spyOn(sdkInternal, "import_ssh_key")
|
||||
.mockImplementationOnce(() => {
|
||||
throw { variant: "PasswordRequired" };
|
||||
})
|
||||
.mockImplementationOnce(() => exampleSshKey);
|
||||
dialogService.open.mockReturnValue({ closed: new BehaviorSubject("password") } as any);
|
||||
platformUtilsService.readFromClipboard.mockResolvedValue("ssh_key");
|
||||
|
||||
expect(await sshImportPromptService.importSshKeyFromClipboard()).toEqual(exampleSshKeyData);
|
||||
expect(dialogService.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("cancels when no password was provided", async () => {
|
||||
jest.spyOn(sdkInternal, "import_ssh_key").mockImplementationOnce(() => {
|
||||
throw { variant: "PasswordRequired" };
|
||||
});
|
||||
dialogService.open.mockReturnValue({ closed: new BehaviorSubject("") } as any);
|
||||
platformUtilsService.readFromClipboard.mockResolvedValue("ssh_key");
|
||||
|
||||
expect(await sshImportPromptService.importSshKeyFromClipboard()).toEqual(null);
|
||||
expect(dialogService.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes through error on no password", async () => {
|
||||
jest.spyOn(sdkInternal, "import_ssh_key").mockImplementationOnce(() => {
|
||||
throw { variant: "UnsupportedKeyType" };
|
||||
});
|
||||
platformUtilsService.readFromClipboard.mockResolvedValue("ssh_key");
|
||||
|
||||
expect(await sshImportPromptService.importSshKeyFromClipboard()).toEqual(null);
|
||||
expect(i18nService.t).toHaveBeenCalledWith("sshKeyTypeUnsupported");
|
||||
});
|
||||
|
||||
it("passes through error with password", async () => {
|
||||
jest
|
||||
.spyOn(sdkInternal, "import_ssh_key")
|
||||
.mockClear()
|
||||
.mockImplementationOnce(() => {
|
||||
throw { variant: "PasswordRequired" };
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
throw { variant: "UnsupportedKeyType" };
|
||||
});
|
||||
platformUtilsService.readFromClipboard.mockResolvedValue("ssh_key");
|
||||
dialogService.open.mockReturnValue({ closed: new BehaviorSubject("password") } as any);
|
||||
|
||||
expect(await sshImportPromptService.importSshKeyFromClipboard()).toEqual(null);
|
||||
expect(i18nService.t).toHaveBeenCalledWith("sshKeyTypeUnsupported");
|
||||
});
|
||||
});
|
||||
});
|
||||
5
libs/vault/src/services/ssh-import-prompt.service.ts
Normal file
5
libs/vault/src/services/ssh-import-prompt.service.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SshKeyData } from "@bitwarden/common/vault/models/data/ssh-key.data";
|
||||
|
||||
export abstract class SshImportPromptService {
|
||||
abstract importSshKeyFromClipboard: () => Promise<SshKeyData | null>;
|
||||
}
|
||||
@@ -8,11 +8,13 @@
|
||||
"@bitwarden/auth/common": ["../auth/src/common"],
|
||||
"@bitwarden/common/*": ["../common/src/*"],
|
||||
"@bitwarden/components": ["../components/src"],
|
||||
"@bitwarden/importer-ui": ["../importer/src/components"],
|
||||
"@bitwarden/generator-components": ["../tools/generator/components/src"],
|
||||
"@bitwarden/generator-core": ["../tools/generator/core/src"],
|
||||
"@bitwarden/generator-history": ["../tools/generator/extensions/history/src"],
|
||||
"@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"],
|
||||
"@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"],
|
||||
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"],
|
||||
"@bitwarden/key-management": ["../key-management/src"],
|
||||
"@bitwarden/platform": ["../platform/src"],
|
||||
"@bitwarden/ui-common": ["../ui/common/src"],
|
||||
|
||||
Reference in New Issue
Block a user