1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

[EC-598] feat: differntiate between resident auth and 2fa

This commit is contained in:
Andreas Coroiu
2023-01-05 15:29:24 +01:00
parent 132c3fe04d
commit f7a74c8cf2
6 changed files with 66 additions and 9 deletions

View File

@@ -1,7 +1,18 @@
<ng-container *ngIf="data"> <ng-container *ngIf="data">
<div class="auth-wrapper"> <div class="auth-wrapper">
<ng-container *ngIf="data.type == 'ConfirmCredentialRequest'">
A site is asking for authentication using the following credential:
<div class="box list">
<div class="box-content">
<app-cipher-row [cipher]="ciphers[0]"></app-cipher-row>
</div>
</div>
<button type="button" class="btn btn-outline-secondary" (click)="confirm()">
Authenticate
</button>
</ng-container>
<ng-container *ngIf="data.type == 'PickCredentialRequest'"> <ng-container *ngIf="data.type == 'PickCredentialRequest'">
A site is asking for authentication, please choose one of the following credentials to use A site is asking for authentication, please choose one of the following credentials to use:
<div class="box list"> <div class="box list">
<div class="box-content"> <div class="box-content">
<app-cipher-row <app-cipher-row
@@ -11,10 +22,6 @@
></app-cipher-row> ></app-cipher-row>
</div> </div>
</div> </div>
<!-- <button type="button" class="btn btn-outline-secondary" (click)="accept()">
<ng-container *ngIf="data.type == 'VerifyUserRequest'">Authenticate</ng-container>
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">Create</ng-container>
</button> -->
</ng-container> </ng-container>
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'"> <ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">
A site wants to create the following passkey in your vault A site wants to create the following passkey in your vault
@@ -23,9 +30,7 @@
<app-cipher-row [cipher]="ciphers[0]"></app-cipher-row> <app-cipher-row [cipher]="ciphers[0]"></app-cipher-row>
</div> </div>
</div> </div>
<button type="button" class="btn btn-outline-secondary" (click)="confirm()"> <button type="button" class="btn btn-outline-secondary" (click)="confirmNew()">Create</button>
<ng-container *ngIf="data.type == 'ConfirmNewCredentialRequest'">Create</ng-container>
</button>
</ng-container> </ng-container>
<button type="button" class="btn btn-outline-secondary" (click)="cancel(true)"> <button type="button" class="btn btn-outline-secondary" (click)="cancel(true)">
Use browser built-in Use browser built-in

View File

@@ -37,6 +37,9 @@ export class Fido2Component implements OnInit, OnDestroy {
cipher.type = CipherType.Fido2Key; cipher.type = CipherType.Fido2Key;
cipher.fido2Key = new Fido2KeyView(); cipher.fido2Key = new Fido2KeyView();
this.ciphers = [cipher]; this.ciphers = [cipher];
} else if (this.data?.type === "ConfirmCredentialRequest") {
const cipher = await this.cipherService.get(this.data.cipherId);
this.ciphers = [await cipher.decrypt()];
} else if (this.data?.type === "PickCredentialRequest") { } else if (this.data?.type === "PickCredentialRequest") {
this.ciphers = await Promise.all( this.ciphers = await Promise.all(
this.data.cipherIds.map(async (cipherId) => { this.data.cipherIds.map(async (cipherId) => {
@@ -62,6 +65,14 @@ export class Fido2Component implements OnInit, OnDestroy {
} }
confirm() { confirm() {
BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId,
type: "ConfirmCredentialResponse",
});
window.close();
}
confirmNew() {
BrowserFido2UserInterfaceService.sendMessage({ BrowserFido2UserInterfaceService.sendMessage({
requestId: this.data.requestId, requestId: this.data.requestId,
type: "ConfirmNewCredentialResponse", type: "ConfirmNewCredentialResponse",

View File

@@ -21,6 +21,13 @@ export type BrowserFido2Message = { requestId: string } & (
type: "PickCredentialResponse"; type: "PickCredentialResponse";
cipherId?: string; cipherId?: string;
} }
| {
type: "ConfirmCredentialRequest";
cipherId: string;
}
| {
type: "ConfirmCredentialResponse";
}
| { | {
type: "ConfirmNewCredentialRequest"; type: "ConfirmNewCredentialRequest";
name: string; name: string;
@@ -50,6 +57,35 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
BrowserApi.messageListener(BrowserFido2MessageName, this.processMessage.bind(this)); BrowserApi.messageListener(BrowserFido2MessageName, this.processMessage.bind(this));
} }
async confirmCredential(cipherId: string): Promise<boolean> {
const requestId = Utils.newGuid();
const data: BrowserFido2Message = { type: "ConfirmCredentialRequest", cipherId, requestId };
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
this.popupUtilsService.popOut(
null,
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
{ center: true }
);
const response = await lastValueFrom(
this.messages$.pipe(
filter((msg) => msg.requestId === requestId),
first(),
takeUntil(this.destroy$)
)
);
if (response.type === "ConfirmCredentialResponse") {
return true;
}
if (response.type === "RequestCancelled") {
throw new RequestAbortedError(response.fallbackRequested);
}
return false;
}
async pickCredential(cipherIds: string[]): Promise<string> { async pickCredential(cipherIds: string[]): Promise<string> {
const requestId = Utils.newGuid(); const requestId = Utils.newGuid();
const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId }; const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId };

View File

@@ -3,6 +3,7 @@ export interface NewCredentialParams {
} }
export abstract class Fido2UserInterfaceService { export abstract class Fido2UserInterfaceService {
confirmCredential: (cipherId: string) => Promise<boolean>;
pickCredential: (cipherIds: string[]) => Promise<string>; pickCredential: (cipherIds: string[]) => Promise<string>;
confirmNewCredential: (params: NewCredentialParams) => Promise<boolean>; confirmNewCredential: (params: NewCredentialParams) => Promise<boolean>;
} }

View File

@@ -129,7 +129,7 @@ export class Fido2Service implements Fido2ServiceAbstraction {
throw new OriginMismatchError(); throw new OriginMismatchError();
} }
await this.fido2UserInterfaceService.pickCredential([credential.credentialId.encoded]); await this.fido2UserInterfaceService.confirmCredential(credential.credentialId.encoded);
} else { } else {
// We're looking for a resident key // We're looking for a resident key
const credentials = await this.getCredentialsByRp(params.rpId); const credentials = await this.getCredentialsByRp(params.rpId);

View File

@@ -2,6 +2,10 @@ import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } fro
import { RequestAbortedError } from "../../abstractions/fido2/fido2.service.abstraction"; import { RequestAbortedError } from "../../abstractions/fido2/fido2.service.abstraction";
export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction { export class Fido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
async confirmCredential(cipherId: string): Promise<boolean> {
return false;
}
pickCredential(cipherIds: string[]): Promise<string> { pickCredential(cipherIds: string[]): Promise<string> {
throw new RequestAbortedError(); throw new RequestAbortedError();
} }