mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
[EC-598] feat: show new cipher being added
This commit is contained in:
@@ -87,7 +87,7 @@ import { PrivateModeWarningComponent } from "./components/private-mode-warning.c
|
|||||||
import { SendListComponent } from "./components/send-list.component";
|
import { SendListComponent } from "./components/send-list.component";
|
||||||
import { SetPinComponent } from "./components/set-pin.component";
|
import { SetPinComponent } from "./components/set-pin.component";
|
||||||
import { UserVerificationComponent } from "./components/user-verification.component";
|
import { UserVerificationComponent } from "./components/user-verification.component";
|
||||||
import { Fido2Module } from "./fido2/fido2.module";
|
import { Fido2Component } from "./fido2/fido2.component";
|
||||||
import { GeneratorComponent } from "./generator/generator.component";
|
import { GeneratorComponent } from "./generator/generator.component";
|
||||||
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
|
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
|
||||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
|
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
|
||||||
@@ -193,7 +193,6 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
Fido2Module,
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ActionButtonsComponent,
|
ActionButtonsComponent,
|
||||||
@@ -246,6 +245,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
|||||||
RemovePasswordComponent,
|
RemovePasswordComponent,
|
||||||
VaultSelectComponent,
|
VaultSelectComponent,
|
||||||
AboutComponent,
|
AboutComponent,
|
||||||
|
Fido2Component,
|
||||||
],
|
],
|
||||||
providers: [CurrencyPipe, DatePipe],
|
providers: [CurrencyPipe, DatePipe],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<div class="auth-wrapper">
|
<ng-container *ngIf="data">
|
||||||
<ng-container *ngIf="data.type == 'VerifyUserRequest'">
|
<div class="auth-wrapper">
|
||||||
A site is asking for authentication
|
<ng-container *ngIf="data.type == 'VerifyUserRequest'">
|
||||||
</ng-container>
|
A site is asking for authentication
|
||||||
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">
|
</ng-container>
|
||||||
A site wants to create a new passkey in your vault
|
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">
|
||||||
</ng-container>
|
<div class="box list">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="accept()">
|
<div class="box-content">
|
||||||
<ng-container *ngIf="data.type == 'VerifyUserRequest'">Authenticate</ng-container>
|
<app-cipher-row [cipher]="cipher"></app-cipher-row>
|
||||||
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">Create</ng-container>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel(true)">
|
A site wants to create a new passkey in your vault
|
||||||
Use browser built-in
|
</ng-container>
|
||||||
</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="accept()">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel(false)">Abort</button>
|
<ng-container *ngIf="data.type == 'VerifyUserRequest'">Authenticate</ng-container>
|
||||||
</div>
|
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">Create</ng-container>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel(true)">
|
||||||
|
Use browser built-in
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel(false)">Abort</button>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { Component, HostListener } from "@angular/core";
|
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||||
|
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||||
|
import { Fido2KeyView } from "@bitwarden/common/models/view/fido2-key.view";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BrowserFido2Message,
|
BrowserFido2Message,
|
||||||
@@ -11,11 +16,25 @@ import {
|
|||||||
templateUrl: "fido2.component.html",
|
templateUrl: "fido2.component.html",
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
})
|
})
|
||||||
export class Fido2Component {
|
export class Fido2Component implements OnInit, OnDestroy {
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
protected data?: BrowserFido2Message;
|
||||||
|
protected cipher?: CipherView;
|
||||||
|
|
||||||
constructor(private activatedRoute: ActivatedRoute) {}
|
constructor(private activatedRoute: ActivatedRoute) {}
|
||||||
|
|
||||||
get data() {
|
ngOnInit(): void {
|
||||||
return this.activatedRoute.snapshot.queryParams as BrowserFido2Message;
|
this.activatedRoute.queryParamMap.pipe(takeUntil(this.destroy$)).subscribe((queryParamMap) => {
|
||||||
|
this.data = JSON.parse(queryParamMap.get("data"));
|
||||||
|
|
||||||
|
if (this.data?.type === "ConfirmNewCredentialRequest") {
|
||||||
|
this.cipher = new CipherView();
|
||||||
|
this.cipher.name = this.data.name;
|
||||||
|
this.cipher.type = CipherType.Fido2Key;
|
||||||
|
this.cipher.fido2Key = new Fido2KeyView();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async accept() {
|
async accept() {
|
||||||
@@ -56,4 +75,9 @@ export class Fido2Component {
|
|||||||
fallbackRequested: fallback,
|
fallbackRequested: fallback,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { NgModule } from "@angular/core";
|
|
||||||
|
|
||||||
import { Fido2Component } from "./fido2.component";
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule],
|
|
||||||
declarations: [Fido2Component],
|
|
||||||
exports: [Fido2Component],
|
|
||||||
})
|
|
||||||
export class Fido2Module {}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { filter, first, lastValueFrom, Subject, takeUntil } from "rxjs";
|
import { filter, first, lastValueFrom, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/abstractions/fido2/fido2-user-interface.service.abstraction";
|
import {
|
||||||
|
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||||
|
NewCredentialParams,
|
||||||
|
} from "@bitwarden/common/abstractions/fido2/fido2-user-interface.service.abstraction";
|
||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
|
||||||
import { RequestAbortedError } from "../../../../../libs/common/src/abstractions/fido2/fido2.service.abstraction";
|
import { RequestAbortedError } from "../../../../../libs/common/src/abstractions/fido2/fido2.service.abstraction";
|
||||||
@@ -18,6 +21,7 @@ export type BrowserFido2Message = { requestId: string } & (
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "ConfirmNewCredentialRequest";
|
type: "ConfirmNewCredentialRequest";
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "ConfirmNewCredentialResponse";
|
type: "ConfirmNewCredentialResponse";
|
||||||
@@ -51,7 +55,7 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
async verifyPresence(): Promise<boolean> {
|
async verifyPresence(): Promise<boolean> {
|
||||||
const requestId = Utils.newGuid();
|
const requestId = Utils.newGuid();
|
||||||
const data: BrowserFido2Message = { type: "VerifyUserRequest", requestId };
|
const data: BrowserFido2Message = { type: "VerifyUserRequest", requestId };
|
||||||
const queryParams = new URLSearchParams(data).toString();
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
this.popupUtilsService.popOut(
|
this.popupUtilsService.popOut(
|
||||||
null,
|
null,
|
||||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||||
@@ -77,10 +81,10 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmNewCredential(): Promise<boolean> {
|
async confirmNewCredential({ name }: NewCredentialParams): Promise<boolean> {
|
||||||
const requestId = Utils.newGuid();
|
const requestId = Utils.newGuid();
|
||||||
const data: BrowserFido2Message = { type: "ConfirmNewCredentialRequest", requestId };
|
const data: BrowserFido2Message = { type: "ConfirmNewCredentialRequest", requestId, name };
|
||||||
const queryParams = new URLSearchParams(data).toString();
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
this.popupUtilsService.popOut(
|
this.popupUtilsService.popOut(
|
||||||
null,
|
null,
|
||||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
export interface NewCredentialParams {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class Fido2UserInterfaceService {
|
export abstract class Fido2UserInterfaceService {
|
||||||
verifyUser: () => Promise<boolean>;
|
verifyUser: () => Promise<boolean>;
|
||||||
verifyPresence: () => Promise<boolean>;
|
verifyPresence: () => Promise<boolean>;
|
||||||
confirmNewCredential: () => Promise<boolean>;
|
confirmNewCredential: (params: NewCredentialParams) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
async createCredential(
|
async createCredential(
|
||||||
params: CredentialRegistrationParams
|
params: CredentialRegistrationParams
|
||||||
): Promise<CredentialRegistrationResult> {
|
): Promise<CredentialRegistrationResult> {
|
||||||
const presence = await this.fido2UserInterfaceService.confirmNewCredential();
|
const presence = await this.fido2UserInterfaceService.confirmNewCredential({
|
||||||
|
name: params.origin,
|
||||||
|
});
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log("Fido2Service.createCredential", params);
|
console.log("Fido2Service.createCredential", params);
|
||||||
|
|
||||||
@@ -123,14 +125,11 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
credential = await this.getCredentialByRp(params.rpId);
|
credential = await this.getCredentialByRp(params.rpId);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Found credential: ", credential);
|
|
||||||
|
|
||||||
if (credential === undefined) {
|
if (credential === undefined) {
|
||||||
throw new NoCredentialFoundError();
|
throw new NoCredentialFoundError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credential.origin !== params.origin) {
|
if (credential.origin !== params.origin) {
|
||||||
console.error(`${params.origin} tried to use credential created by ${credential.origin}`);
|
|
||||||
throw new OriginMismatchError();
|
throw new OriginMismatchError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,8 +202,6 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
view.fido2Key.rpId = credential.rpId;
|
view.fido2Key.rpId = credential.rpId;
|
||||||
view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle);
|
view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle);
|
||||||
|
|
||||||
console.log("saving credential", { view, credential });
|
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(view);
|
const cipher = await this.cipherService.encrypt(view);
|
||||||
await this.cipherService.createWithServer(cipher);
|
await this.cipherService.createWithServer(cipher);
|
||||||
|
|
||||||
@@ -237,22 +234,16 @@ interface AuthDataParams {
|
|||||||
|
|
||||||
async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
|
async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
|
||||||
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.key);
|
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.key);
|
||||||
let privateKey;
|
const privateKey = await crypto.subtle.importKey(
|
||||||
try {
|
"pkcs8",
|
||||||
privateKey = await crypto.subtle.importKey(
|
keyBuffer,
|
||||||
"pkcs8",
|
{
|
||||||
keyBuffer,
|
name: "ECDSA",
|
||||||
{
|
namedCurve: "P-256",
|
||||||
name: "ECDSA",
|
},
|
||||||
namedCurve: "P-256",
|
true,
|
||||||
},
|
KeyUsages
|
||||||
true,
|
);
|
||||||
KeyUsages
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Error importing key", { err });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
credentialId: new CredentialId(cipherView.id),
|
credentialId: new CredentialId(cipherView.id),
|
||||||
|
|||||||
Reference in New Issue
Block a user