1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-11 14:48:46 +01:00

[PM-14990] Add password prompt for ssh key import (#12105)

* Add password prompt for ssh key import

* Remove empty line

* Convert to switch statement
This commit is contained in:
Bernd Schoolmann 2024-11-27 08:29:36 -08:00 committed by GitHub
parent 2767851925
commit f79141c421
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 147 additions and 30 deletions

View File

@ -207,6 +207,21 @@
"sshKeyGenerated": { "sshKeyGenerated": {
"message": "A new SSH key was generated" "message": "A new SSH key was generated"
}, },
"sshKeyWrongPassword": {
"message": "The password you entered is incorrect."
},
"importSshKey": {
"message": "Import"
},
"confirmSshKeyPassword": {
"message": "Confirm password"
},
"enterSshKeyPasswordDesc": {
"message": "Enter the password for the SSH key."
},
"enterSshKeyPassword": {
"message": "Enter password"
},
"sshAgentUnlockRequired": { "sshAgentUnlockRequired": {
"message": "Please unlock your vault to approve the SSH key request." "message": "Please unlock your vault to approve the SSH key request."
}, },

View File

@ -2,6 +2,7 @@ import { DatePipe } from "@angular/common";
import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { NgForm } from "@angular/forms"; import { NgForm } from "@angular/forms";
import { sshagent as sshAgent } from "desktop_native/napi"; import { sshagent as sshAgent } from "desktop_native/napi";
import { lastValueFrom } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common"; import { CollectionService } from "@bitwarden/admin-console/common";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component";
@ -22,6 +23,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui";
import { PasswordRepromptService } from "@bitwarden/vault"; import { PasswordRepromptService } from "@bitwarden/vault";
const BroadcasterSubscriptionId = "AddEditComponent"; const BroadcasterSubscriptionId = "AddEditComponent";
@ -170,33 +172,47 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
} }
} }
async importSshKeyFromClipboard() { async importSshKeyFromClipboard(password: string = "") {
const key = await this.platformUtilsService.readFromClipboard(); const key = await this.platformUtilsService.readFromClipboard();
const parsedKey = await ipc.platform.sshAgent.importKey(key, ""); const parsedKey = await ipc.platform.sshAgent.importKey(key, password);
if (parsedKey == null || parsedKey.status === sshAgent.SshKeyImportStatus.ParsingError) { if (parsedKey == null) {
this.toastService.showToast({ this.toastService.showToast({
variant: "error", variant: "error",
title: "", title: "",
message: this.i18nService.t("invalidSshKey"), message: this.i18nService.t("invalidSshKey"),
}); });
return; return;
} else if (parsedKey.status === sshAgent.SshKeyImportStatus.UnsupportedKeyType) { }
switch (parsedKey.status) {
case sshAgent.SshKeyImportStatus.ParsingError:
this.toastService.showToast({
variant: "error",
title: "",
message: this.i18nService.t("invalidSshKey"),
});
return;
case sshAgent.SshKeyImportStatus.UnsupportedKeyType:
this.toastService.showToast({ this.toastService.showToast({
variant: "error", variant: "error",
title: "", title: "",
message: this.i18nService.t("sshKeyTypeUnsupported"), message: this.i18nService.t("sshKeyTypeUnsupported"),
}); });
} else if ( return;
parsedKey.status === sshAgent.SshKeyImportStatus.PasswordRequired || case sshAgent.SshKeyImportStatus.PasswordRequired:
parsedKey.status === sshAgent.SshKeyImportStatus.WrongPassword case sshAgent.SshKeyImportStatus.WrongPassword:
) { if (password !== "") {
this.toastService.showToast({ this.toastService.showToast({
variant: "error", variant: "error",
title: "", title: "",
message: this.i18nService.t("sshKeyPasswordUnsupported"), message: this.i18nService.t("sshKeyWrongPassword"),
}); });
return;
} else { } else {
password = await this.getSshKeyPassword();
await this.importSshKeyFromClipboard(password);
}
return;
default:
this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey; this.cipher.sshKey.privateKey = parsedKey.sshKey.privateKey;
this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey; this.cipher.sshKey.publicKey = parsedKey.sshKey.publicKey;
this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint; this.cipher.sshKey.keyFingerprint = parsedKey.sshKey.keyFingerprint;
@ -208,6 +224,14 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
} }
} }
async getSshKeyPassword(): Promise<string> {
const dialog = this.dialogService.open<string>(SshKeyPasswordPromptComponent, {
ariaModal: true,
});
return await lastValueFrom(dialog.closed);
}
async typeChange() { async typeChange() {
if (this.cipher.type === CipherType.SshKey) { if (this.cipher.type === CipherType.SshKey) {
await this.generateSshKey(); await this.generateSshKey();

View File

@ -1,3 +1,4 @@
export * from "./import-error-dialog.component"; export * from "./import-error-dialog.component";
export * from "./import-success-dialog.component"; export * from "./import-success-dialog.component";
export * from "./file-password-prompt.component"; export * from "./file-password-prompt.component";
export * from "./sshkey-password-prompt.component";

View File

@ -0,0 +1,31 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog>
<span bitDialogTitle>
{{ "enterSshKeyPassword" | i18n }}
</span>
<div bitDialogContent>
{{ "enterSshKeyPasswordDesc" | i18n }}
<bit-form-field class="tw-mt-6">
<bit-label>{{ "confirmSshKeyPassword" | i18n }}</bit-label>
<input
bitInput
type="password"
formControlName="sshKeyPassword"
appAutofocus
appInputVerbatim
/>
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
</div>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" type="submit">
<span>{{ "importSshKey" | i18n }}</span>
</button>
<button bitButton bitDialogClose buttonType="secondary" type="button">
<span>{{ "cancel" | i18n }}</span>
</button>
</ng-container>
</bit-dialog>
</form>

View File

@ -0,0 +1,46 @@
import { DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
FormFieldModule,
IconButtonModule,
} from "@bitwarden/components";
@Component({
templateUrl: "sshkey-password-prompt.component.html",
standalone: true,
imports: [
CommonModule,
JslibModule,
DialogModule,
FormFieldModule,
AsyncActionsModule,
ButtonModule,
IconButtonModule,
ReactiveFormsModule,
],
})
export class SshKeyPasswordPromptComponent {
protected formGroup = this.formBuilder.group({
sshKeyPassword: ["", Validators.required],
});
constructor(
public dialogRef: DialogRef,
protected formBuilder: FormBuilder,
) {}
submit = () => {
this.formGroup.markAsTouched();
if (!this.formGroup.valid) {
return;
}
this.dialogRef.close(this.formGroup.value.sshKeyPassword);
};
}