mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-20 21:01:29 +01:00
[EC-598] feat: fully refactored user interface
Now uses sessions instead of single request-response style communcation
This commit is contained in:
parent
11d340bc97
commit
cd70b17b9a
@ -1,6 +1,17 @@
|
||||
import { filter, first, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
EmptyError,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
fromEvent,
|
||||
Observable,
|
||||
Subject,
|
||||
take,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { UserRequestedFallbackAbortReason } from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
|
||||
import {
|
||||
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||
Fido2UserInterfaceSession,
|
||||
@ -12,19 +23,26 @@ import { PopupUtilsService } from "../../popup/services/popup-utils.service";
|
||||
|
||||
const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage";
|
||||
|
||||
export class Fido2Error extends Error {
|
||||
constructor(message: string, readonly fallbackRequested = false) {
|
||||
super(message);
|
||||
export class SessionClosedError extends Error {
|
||||
constructor() {
|
||||
super("Fido2UserInterfaceSession was closed");
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestAbortedError extends Fido2Error {
|
||||
constructor(fallbackRequested = false) {
|
||||
super("Fido2 request was aborted", fallbackRequested);
|
||||
}
|
||||
}
|
||||
|
||||
export type BrowserFido2Message = { requestId: string } & (
|
||||
export type BrowserFido2Message = { sessionId: string } & (
|
||||
| /**
|
||||
* This message is used by popouts to announce that they are ready
|
||||
* to recieve messages.
|
||||
**/ {
|
||||
type: "ConnectResponse";
|
||||
}
|
||||
/**
|
||||
* This message is used to announce the creation of a new session.
|
||||
* It iss used by popouts to know when to close.
|
||||
**/
|
||||
| {
|
||||
type: "NewSessionCreatedRequest";
|
||||
}
|
||||
| {
|
||||
type: "PickCredentialRequest";
|
||||
cipherIds: string[];
|
||||
@ -66,228 +84,77 @@ export type BrowserFido2Message = { requestId: string } & (
|
||||
}
|
||||
);
|
||||
|
||||
export interface BrowserFido2UserInterfaceRequestData {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
|
||||
static sendMessage(msg: BrowserFido2Message) {
|
||||
BrowserApi.sendMessage(BrowserFido2MessageName, msg);
|
||||
}
|
||||
|
||||
static onAbort$(requestId: string): Observable<BrowserFido2Message> {
|
||||
const messages$ = BrowserApi.messageListener$() as Observable<BrowserFido2Message>;
|
||||
return messages$.pipe(
|
||||
filter((message) => message.type === "AbortRequest" && message.requestId === requestId),
|
||||
first()
|
||||
);
|
||||
}
|
||||
|
||||
private messages$ = BrowserApi.messageListener$() as Observable<BrowserFido2Message>;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private popupUtilsService: PopupUtilsService) {}
|
||||
|
||||
async newSession(abortController?: AbortController): Promise<Fido2UserInterfaceSession> {
|
||||
return await BrowserFido2UserInterfaceSession.create(this, abortController);
|
||||
}
|
||||
|
||||
async confirmCredential(
|
||||
cipherId: string,
|
||||
abortController = new AbortController()
|
||||
): Promise<boolean> {
|
||||
const requestId = Utils.newGuid();
|
||||
const data: BrowserFido2Message = { type: "ConfirmCredentialRequest", cipherId, requestId };
|
||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||
|
||||
const abortHandler = () =>
|
||||
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||
abortController.signal.addEventListener("abort", abortHandler);
|
||||
|
||||
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 === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async pickCredential(
|
||||
cipherIds: string[],
|
||||
abortController = new AbortController()
|
||||
): Promise<string> {
|
||||
const requestId = Utils.newGuid();
|
||||
const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId };
|
||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||
|
||||
const abortHandler = () =>
|
||||
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||
abortController.signal.addEventListener("abort", abortHandler);
|
||||
|
||||
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 === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
if (response.type !== "PickCredentialResponse") {
|
||||
throw new RequestAbortedError();
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return response.cipherId;
|
||||
}
|
||||
|
||||
async confirmNewCredential(
|
||||
{ credentialName, userName }: NewCredentialParams,
|
||||
abortController = new AbortController()
|
||||
): Promise<boolean> {
|
||||
const requestId = Utils.newGuid();
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmNewCredentialRequest",
|
||||
requestId,
|
||||
credentialName,
|
||||
userName,
|
||||
};
|
||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||
|
||||
const abortHandler = () =>
|
||||
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||
abortController.signal.addEventListener("abort", abortHandler);
|
||||
|
||||
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 === "ConfirmNewCredentialResponse") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.type === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async confirmNewNonDiscoverableCredential(
|
||||
{ credentialName, userName }: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
): Promise<string> {
|
||||
const requestId = Utils.newGuid();
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmNewNonDiscoverableCredentialRequest",
|
||||
requestId,
|
||||
credentialName,
|
||||
userName,
|
||||
};
|
||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||
|
||||
const abortHandler = () =>
|
||||
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||
abortController.signal.addEventListener("abort", abortHandler);
|
||||
|
||||
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 === "ConfirmNewNonDiscoverableCredentialResponse") {
|
||||
return response.cipherId;
|
||||
}
|
||||
|
||||
if (response.type === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async informExcludedCredential(
|
||||
existingCipherIds: string[],
|
||||
newCredential: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
): Promise<void> {
|
||||
// Not Implemented
|
||||
}
|
||||
|
||||
private setAbortTimeout(abortController: AbortController) {
|
||||
return setTimeout(() => abortController.abort());
|
||||
return await BrowserFido2UserInterfaceSession.create(this.popupUtilsService, abortController);
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSession {
|
||||
static async create(
|
||||
parentService: BrowserFido2UserInterfaceService,
|
||||
popupUtilsService: PopupUtilsService,
|
||||
abortController?: AbortController
|
||||
): Promise<BrowserFido2UserInterfaceSession> {
|
||||
return new BrowserFido2UserInterfaceSession(parentService, abortController);
|
||||
return new BrowserFido2UserInterfaceSession(popupUtilsService, abortController);
|
||||
}
|
||||
|
||||
readonly abortListener: () => void;
|
||||
static sendMessage(msg: BrowserFido2Message) {
|
||||
BrowserApi.sendMessage(BrowserFido2MessageName, msg);
|
||||
}
|
||||
|
||||
private closed = false;
|
||||
private messages$ = (BrowserApi.messageListener$() as Observable<BrowserFido2Message>).pipe(
|
||||
filter((msg) => msg.sessionId === this.sessionId)
|
||||
);
|
||||
private connected$ = new BehaviorSubject(false);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
private constructor(
|
||||
private readonly parentService: BrowserFido2UserInterfaceService,
|
||||
private readonly popupUtilsService: PopupUtilsService,
|
||||
readonly abortController = new AbortController(),
|
||||
readonly sessionId = Utils.newGuid()
|
||||
) {
|
||||
this.abortListener = () => this.abort();
|
||||
abortController.signal.addEventListener("abort", this.abortListener);
|
||||
this.messages$
|
||||
.pipe(
|
||||
filter((msg) => msg.type === "ConnectResponse"),
|
||||
take(1),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.connected$.next(true);
|
||||
});
|
||||
|
||||
// Handle session aborted by RP
|
||||
fromEvent(abortController.signal, "abort")
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => {
|
||||
this.close();
|
||||
BrowserFido2UserInterfaceSession.sendMessage({
|
||||
type: "AbortRequest",
|
||||
sessionId: this.sessionId,
|
||||
});
|
||||
});
|
||||
|
||||
// Handle session aborted by user
|
||||
this.messages$
|
||||
.pipe(
|
||||
filter((msg) => msg.type === "AbortResponse"),
|
||||
take(1),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((msg) => {
|
||||
if (msg.type === "AbortResponse") {
|
||||
this.close();
|
||||
this.abortController.abort(UserRequestedFallbackAbortReason);
|
||||
}
|
||||
});
|
||||
|
||||
BrowserFido2UserInterfaceSession.sendMessage({
|
||||
type: "NewSessionCreatedRequest",
|
||||
sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
fallbackRequested = false;
|
||||
@ -296,26 +163,61 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
return this.abortController.signal.aborted;
|
||||
}
|
||||
|
||||
confirmCredential(cipherId: string, abortController?: AbortController): Promise<boolean> {
|
||||
return this.parentService.confirmCredential(cipherId, this.abortController);
|
||||
async confirmCredential(cipherId: string): Promise<boolean> {
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmCredentialRequest",
|
||||
cipherId,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
|
||||
await this.send(data);
|
||||
await this.receive("ConfirmCredentialResponse");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pickCredential(cipherIds: string[], abortController?: AbortController): Promise<string> {
|
||||
return this.parentService.pickCredential(cipherIds, this.abortController);
|
||||
async pickCredential(cipherIds: string[]): Promise<string> {
|
||||
const data: BrowserFido2Message = {
|
||||
type: "PickCredentialRequest",
|
||||
cipherIds,
|
||||
sessionId: this.sessionId,
|
||||
};
|
||||
|
||||
await this.send(data);
|
||||
const response = await this.receive("PickCredentialResponse");
|
||||
|
||||
return response.cipherId;
|
||||
}
|
||||
|
||||
confirmNewCredential(
|
||||
params: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
): Promise<boolean> {
|
||||
return this.parentService.confirmNewCredential(params, this.abortController);
|
||||
async confirmNewCredential({ credentialName, userName }: NewCredentialParams): Promise<boolean> {
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmNewCredentialRequest",
|
||||
sessionId: this.sessionId,
|
||||
credentialName,
|
||||
userName,
|
||||
};
|
||||
|
||||
await this.send(data);
|
||||
await this.receive("ConfirmNewCredentialResponse");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
confirmNewNonDiscoverableCredential(
|
||||
params: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
): Promise<string> {
|
||||
return this.parentService.confirmNewNonDiscoverableCredential(params, this.abortController);
|
||||
async confirmNewNonDiscoverableCredential({
|
||||
credentialName,
|
||||
userName,
|
||||
}: NewCredentialParams): Promise<string> {
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmNewNonDiscoverableCredentialRequest",
|
||||
sessionId: this.sessionId,
|
||||
credentialName,
|
||||
userName,
|
||||
};
|
||||
|
||||
await this.send(data);
|
||||
const response = await this.receive("ConfirmNewNonDiscoverableCredentialResponse");
|
||||
|
||||
return response.cipherId;
|
||||
}
|
||||
|
||||
informExcludedCredential(
|
||||
@ -323,18 +225,52 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
|
||||
newCredential: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
): Promise<void> {
|
||||
return this.parentService.informExcludedCredential(
|
||||
existingCipherIds,
|
||||
newCredential,
|
||||
this.abortController
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
private abort() {
|
||||
this.close();
|
||||
private async send(msg: BrowserFido2Message): Promise<void> {
|
||||
if (!this.connected$.value) {
|
||||
await this.connect();
|
||||
}
|
||||
BrowserFido2UserInterfaceSession.sendMessage(msg);
|
||||
}
|
||||
|
||||
private async receive<T extends BrowserFido2Message["type"]>(
|
||||
type: T
|
||||
): Promise<BrowserFido2Message & { type: T }> {
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.messages$.pipe(
|
||||
filter((msg) => msg.sessionId === this.sessionId && msg.type === type),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
);
|
||||
return response as BrowserFido2Message & { type: T };
|
||||
} catch (error) {
|
||||
if (error instanceof EmptyError) {
|
||||
throw new SessionClosedError();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async connect(): Promise<void> {
|
||||
if (this.closed) {
|
||||
throw new Error("Cannot re-open closed session");
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({ sessionId: this.sessionId }).toString();
|
||||
this.popupUtilsService.popOut(
|
||||
null,
|
||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||
{ center: true }
|
||||
);
|
||||
await firstValueFrom(this.connected$.pipe(filter((connected) => connected === true)));
|
||||
}
|
||||
|
||||
private close() {
|
||||
this.abortController.signal.removeEventListener("abort", this.abortListener);
|
||||
this.closed = true;
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="data">
|
||||
<ng-container *ngIf="data$ | async as data">
|
||||
<div class="auth-wrapper">
|
||||
<ng-container *ngIf="data.type == 'ConfirmCredentialRequest'">
|
||||
A site is asking for authentication using the following credential:
|
||||
@ -37,9 +37,9 @@
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="confirmNew()">Create</button>
|
||||
</ng-container>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel(true)">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="abort(true)">
|
||||
Use browser built-in
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel(false)">Abort</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="abort(false)">Abort</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -1,15 +1,25 @@
|
||||
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, switchMap, takeUntil } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
concatMap,
|
||||
map,
|
||||
Observable,
|
||||
Subject,
|
||||
take,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { Fido2KeyView } from "@bitwarden/common/webauthn/models/view/fido2-key.view";
|
||||
|
||||
import { BrowserApi } from "../../../browser/browserApi";
|
||||
import {
|
||||
BrowserFido2Message,
|
||||
BrowserFido2UserInterfaceService,
|
||||
BrowserFido2UserInterfaceSession,
|
||||
} from "../../../services/fido2/browser-fido2-user-interface.service";
|
||||
|
||||
@Component({
|
||||
@ -20,35 +30,58 @@ import {
|
||||
export class Fido2Component implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected data?: BrowserFido2Message;
|
||||
protected data$ = new BehaviorSubject<BrowserFido2Message>(null);
|
||||
protected sessionId?: string;
|
||||
protected ciphers?: CipherView[] = [];
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private cipherService: CipherService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.activatedRoute.queryParamMap
|
||||
.pipe(
|
||||
concatMap(async (queryParamMap) => {
|
||||
this.data = JSON.parse(queryParamMap.get("data"));
|
||||
const sessionId$ = this.activatedRoute.queryParamMap.pipe(
|
||||
take(1),
|
||||
map((queryParamMap) => queryParamMap.get("sessionId"))
|
||||
);
|
||||
|
||||
if (this.data?.type === "ConfirmNewCredentialRequest") {
|
||||
combineLatest([sessionId$, BrowserApi.messageListener$() as Observable<BrowserFido2Message>])
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(([sessionId, message]) => {
|
||||
this.sessionId = sessionId;
|
||||
if (message.type === "NewSessionCreatedRequest" && message.sessionId !== sessionId) {
|
||||
return this.abort(false);
|
||||
}
|
||||
|
||||
if (message.sessionId !== sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === "AbortRequest") {
|
||||
return this.abort(false);
|
||||
}
|
||||
|
||||
this.data$.next(message);
|
||||
});
|
||||
|
||||
this.data$
|
||||
.pipe(
|
||||
concatMap(async (data) => {
|
||||
if (data?.type === "ConfirmNewCredentialRequest") {
|
||||
const cipher = new CipherView();
|
||||
cipher.name = this.data.credentialName;
|
||||
cipher.name = data.credentialName;
|
||||
cipher.type = CipherType.Fido2Key;
|
||||
cipher.fido2Key = new Fido2KeyView();
|
||||
cipher.fido2Key.userName = this.data.userName;
|
||||
cipher.fido2Key.userName = data.userName;
|
||||
this.ciphers = [cipher];
|
||||
} else if (this.data?.type === "ConfirmCredentialRequest") {
|
||||
const cipher = await this.cipherService.get(this.data.cipherId);
|
||||
} else if (data?.type === "ConfirmCredentialRequest") {
|
||||
const cipher = await this.cipherService.get(data.cipherId);
|
||||
this.ciphers = [await cipher.decrypt()];
|
||||
} else if (this.data?.type === "PickCredentialRequest") {
|
||||
} else if (data?.type === "PickCredentialRequest") {
|
||||
this.ciphers = await Promise.all(
|
||||
this.data.cipherIds.map(async (cipherId) => {
|
||||
data.cipherIds.map(async (cipherId) => {
|
||||
const cipher = await this.cipherService.get(cipherId);
|
||||
return cipher.decrypt();
|
||||
})
|
||||
);
|
||||
} else if (this.data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||
} else if (data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
||||
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted
|
||||
);
|
||||
@ -58,27 +91,25 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.activatedRoute.queryParamMap
|
||||
.pipe(
|
||||
switchMap((queryParamMap) => {
|
||||
const data = JSON.parse(queryParamMap.get("data"));
|
||||
return BrowserFido2UserInterfaceService.onAbort$(data.requestId);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe(() => this.cancel(false));
|
||||
sessionId$.pipe(takeUntil(this.destroy$)).subscribe((sessionId) => {
|
||||
this.send({
|
||||
sessionId: sessionId,
|
||||
type: "ConnectResponse",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async pick(cipher: CipherView) {
|
||||
if (this.data?.type === "PickCredentialRequest") {
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: this.data.requestId,
|
||||
const data = this.data$.value;
|
||||
if (data?.type === "PickCredentialRequest") {
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
cipherId: cipher.id,
|
||||
type: "PickCredentialResponse",
|
||||
});
|
||||
} else if (this.data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: this.data.requestId,
|
||||
} else if (data?.type === "ConfirmNewNonDiscoverableCredentialRequest") {
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
cipherId: cipher.id,
|
||||
type: "ConfirmNewNonDiscoverableCredentialResponse",
|
||||
});
|
||||
@ -88,31 +119,30 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
confirm() {
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: this.data.requestId,
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
type: "ConfirmCredentialResponse",
|
||||
});
|
||||
window.close();
|
||||
}
|
||||
|
||||
confirmNew() {
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: this.data.requestId,
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
type: "ConfirmNewCredentialResponse",
|
||||
});
|
||||
window.close();
|
||||
}
|
||||
|
||||
cancel(fallback: boolean) {
|
||||
abort(fallback: boolean) {
|
||||
this.unload(fallback);
|
||||
window.close();
|
||||
}
|
||||
|
||||
@HostListener("window:unload")
|
||||
unload(fallback = true) {
|
||||
const data = this.data;
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: data.requestId,
|
||||
unload(fallback = false) {
|
||||
this.send({
|
||||
sessionId: this.sessionId,
|
||||
type: "AbortResponse",
|
||||
fallbackRequested: fallback,
|
||||
});
|
||||
@ -122,4 +152,11 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private send(msg: BrowserFido2Message) {
|
||||
BrowserFido2UserInterfaceSession.sendMessage({
|
||||
sessionId: this.sessionId,
|
||||
...msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const UserRequestedFallbackAbortReason = "UserRequestedFallback";
|
||||
|
||||
export type UserVerification = "discouraged" | "preferred" | "required";
|
||||
|
||||
export abstract class Fido2ClientService {
|
||||
|
Loading…
Reference in New Issue
Block a user