mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-20 21:01:29 +01:00
[EC-598] feat: ability to abort from page script
This commit is contained in:
parent
1ad0bc547a
commit
c7e7ce832b
@ -1,3 +1,5 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||
import { TabMessage } from "../types/tab-messages";
|
||||
|
||||
@ -146,6 +148,14 @@ export class BrowserApi {
|
||||
);
|
||||
}
|
||||
|
||||
static messageListener$() {
|
||||
return new Observable<unknown>((subscriber) => {
|
||||
const handler = (message: unknown) => subscriber.next(message);
|
||||
chrome.runtime.onMessage.addListener(handler);
|
||||
return () => chrome.runtime.onMessage.removeListener(handler);
|
||||
});
|
||||
}
|
||||
|
||||
static sendMessage(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
return chrome.runtime.sendMessage(message);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
import { concatMap, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||
@ -53,6 +53,16 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.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));
|
||||
}
|
||||
|
||||
async pick(cipher: CipherView) {
|
||||
@ -91,7 +101,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
||||
const data = this.data;
|
||||
BrowserFido2UserInterfaceService.sendMessage({
|
||||
requestId: data.requestId,
|
||||
type: "RequestCancelled",
|
||||
type: "AbortResponse",
|
||||
fallbackRequested: fallback,
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { filter, first, lastValueFrom, Subject, takeUntil } from "rxjs";
|
||||
import { filter, first, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import {
|
||||
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||
@ -37,7 +37,10 @@ export type BrowserFido2Message = { requestId: string } & (
|
||||
type: "ConfirmNewCredentialResponse";
|
||||
}
|
||||
| {
|
||||
type: "RequestCancelled";
|
||||
type: "AbortRequest";
|
||||
}
|
||||
| {
|
||||
type: "AbortResponse";
|
||||
fallbackRequested: boolean;
|
||||
}
|
||||
);
|
||||
@ -51,17 +54,31 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
BrowserApi.sendMessage(BrowserFido2MessageName, msg);
|
||||
}
|
||||
|
||||
private messages$ = new Subject<BrowserFido2Message>();
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private popupUtilsService: PopupUtilsService) {
|
||||
BrowserApi.messageListener(BrowserFido2MessageName, this.processMessage.bind(this));
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
async confirmCredential(cipherId: string): Promise<boolean> {
|
||||
private messages$ = BrowserApi.messageListener$() as Observable<BrowserFido2Message>;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private popupUtilsService: PopupUtilsService) {}
|
||||
|
||||
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}`,
|
||||
@ -80,17 +97,27 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.type === "RequestCancelled") {
|
||||
if (response.type === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async pickCredential(cipherIds: string[]): Promise<string> {
|
||||
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}`,
|
||||
@ -105,7 +132,7 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
)
|
||||
);
|
||||
|
||||
if (response.type === "RequestCancelled") {
|
||||
if (response.type === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
@ -113,10 +140,15 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
throw new RequestAbortedError();
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return response.cipherId;
|
||||
}
|
||||
|
||||
async confirmNewCredential({ credentialName, userName }: NewCredentialParams): Promise<boolean> {
|
||||
async confirmNewCredential(
|
||||
{ credentialName, userName }: NewCredentialParams,
|
||||
abortController = new AbortController()
|
||||
): Promise<boolean> {
|
||||
const requestId = Utils.newGuid();
|
||||
const data: BrowserFido2Message = {
|
||||
type: "ConfirmNewCredentialRequest",
|
||||
@ -125,6 +157,11 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
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}`,
|
||||
@ -143,14 +180,12 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response.type === "RequestCancelled") {
|
||||
if (response.type === "AbortResponse") {
|
||||
throw new RequestAbortedError(response.fallbackRequested);
|
||||
}
|
||||
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private processMessage(msg: BrowserFido2Message) {
|
||||
this.messages$.next(msg);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ export interface NewCredentialParams {
|
||||
}
|
||||
|
||||
export abstract class Fido2UserInterfaceService {
|
||||
confirmCredential: (cipherId: string) => Promise<boolean>;
|
||||
pickCredential: (cipherIds: string[]) => Promise<string>;
|
||||
confirmNewCredential: (params: NewCredentialParams) => Promise<boolean>;
|
||||
confirmCredential: (cipherId: string, abortController?: AbortController) => Promise<boolean>;
|
||||
pickCredential: (cipherIds: string[], abortController?: AbortController) => Promise<string>;
|
||||
confirmNewCredential: (
|
||||
params: NewCredentialParams,
|
||||
abortController?: AbortController
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
|
@ -48,10 +48,13 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
||||
params: CredentialRegistrationParams,
|
||||
abortController?: AbortController
|
||||
): Promise<CredentialRegistrationResult> {
|
||||
const presence = await this.fido2UserInterfaceService.confirmNewCredential({
|
||||
credentialName: params.rp.name,
|
||||
userName: params.user.displayName,
|
||||
});
|
||||
const presence = await this.fido2UserInterfaceService.confirmNewCredential(
|
||||
{
|
||||
credentialName: params.rp.name,
|
||||
userName: params.user.displayName,
|
||||
},
|
||||
abortController
|
||||
);
|
||||
|
||||
const attestationFormat = STANDARD_ATTESTATION_FORMAT;
|
||||
const encoder = new TextEncoder();
|
||||
@ -122,7 +125,10 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
||||
};
|
||||
}
|
||||
|
||||
async assertCredential(params: CredentialAssertParams): Promise<CredentialAssertResult> {
|
||||
async assertCredential(
|
||||
params: CredentialAssertParams,
|
||||
abortController?: AbortController
|
||||
): Promise<CredentialAssertResult> {
|
||||
let credential: BitCredential | undefined;
|
||||
|
||||
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
||||
@ -138,7 +144,10 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
||||
// throw new OriginMismatchError();
|
||||
// }
|
||||
|
||||
await this.fido2UserInterfaceService.confirmCredential(credential.credentialId.encoded);
|
||||
await this.fido2UserInterfaceService.confirmCredential(
|
||||
credential.credentialId.encoded,
|
||||
abortController
|
||||
);
|
||||
} else {
|
||||
// We're looking for a resident key
|
||||
const credentials = await this.getCredentialsByRp(params.rpId);
|
||||
@ -148,7 +157,8 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
||||
}
|
||||
|
||||
const pickedId = await this.fido2UserInterfaceService.pickCredential(
|
||||
credentials.map((c) => c.credentialId.encoded)
|
||||
credentials.map((c) => c.credentialId.encoded),
|
||||
abortController
|
||||
);
|
||||
credential = credentials.find((c) => c.credentialId.encoded === pickedId);
|
||||
}
|
||||
@ -184,7 +194,10 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
||||
};
|
||||
}
|
||||
|
||||
private async getCredential(allowedCredentialIds: string[]): Promise<BitCredential | undefined> {
|
||||
private async getCredential(
|
||||
allowedCredentialIds: string[],
|
||||
abortController?: AbortController
|
||||
): Promise<BitCredential | undefined> {
|
||||
let cipher: Cipher | undefined;
|
||||
for (const allowedCredential of allowedCredentialIds) {
|
||||
cipher = await this.cipherService.get(allowedCredential);
|
||||
|
Loading…
Reference in New Issue
Block a user