mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[EC-598] feat: ability to abort from page script
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||||
import { TabMessage } from "../types/tab-messages";
|
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 = {}) {
|
static sendMessage(subscriber: string, arg: any = {}) {
|
||||||
const message = Object.assign({}, { command: subscriber }, arg);
|
const message = Object.assign({}, { command: subscriber }, arg);
|
||||||
return chrome.runtime.sendMessage(message);
|
return chrome.runtime.sendMessage(message);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
|
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
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 { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||||
@@ -53,6 +53,16 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
takeUntil(this.destroy$)
|
takeUntil(this.destroy$)
|
||||||
)
|
)
|
||||||
.subscribe();
|
.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) {
|
async pick(cipher: CipherView) {
|
||||||
@@ -91,7 +101,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
const data = this.data;
|
const data = this.data;
|
||||||
BrowserFido2UserInterfaceService.sendMessage({
|
BrowserFido2UserInterfaceService.sendMessage({
|
||||||
requestId: data.requestId,
|
requestId: data.requestId,
|
||||||
type: "RequestCancelled",
|
type: "AbortResponse",
|
||||||
fallbackRequested: fallback,
|
fallbackRequested: fallback,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { filter, first, lastValueFrom, Subject, takeUntil } from "rxjs";
|
import { filter, first, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
|
||||||
@@ -37,7 +37,10 @@ export type BrowserFido2Message = { requestId: string } & (
|
|||||||
type: "ConfirmNewCredentialResponse";
|
type: "ConfirmNewCredentialResponse";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "RequestCancelled";
|
type: "AbortRequest";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "AbortResponse";
|
||||||
fallbackRequested: boolean;
|
fallbackRequested: boolean;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -51,17 +54,31 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
BrowserApi.sendMessage(BrowserFido2MessageName, msg);
|
BrowserApi.sendMessage(BrowserFido2MessageName, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private messages$ = new Subject<BrowserFido2Message>();
|
static onAbort$(requestId: string): Observable<BrowserFido2Message> {
|
||||||
private destroy$ = new Subject<void>();
|
const messages$ = BrowserApi.messageListener$() as Observable<BrowserFido2Message>;
|
||||||
|
return messages$.pipe(
|
||||||
constructor(private popupUtilsService: PopupUtilsService) {
|
filter((message) => message.type === "AbortRequest" && message.requestId === requestId),
|
||||||
BrowserApi.messageListener(BrowserFido2MessageName, this.processMessage.bind(this));
|
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 requestId = Utils.newGuid();
|
||||||
const data: BrowserFido2Message = { type: "ConfirmCredentialRequest", cipherId, requestId };
|
const data: BrowserFido2Message = { type: "ConfirmCredentialRequest", cipherId, requestId };
|
||||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
|
|
||||||
|
const abortHandler = () =>
|
||||||
|
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||||
|
abortController.signal.addEventListener("abort", abortHandler);
|
||||||
|
|
||||||
this.popupUtilsService.popOut(
|
this.popupUtilsService.popOut(
|
||||||
null,
|
null,
|
||||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||||
@@ -80,17 +97,27 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.type === "RequestCancelled") {
|
if (response.type === "AbortResponse") {
|
||||||
throw new RequestAbortedError(response.fallbackRequested);
|
throw new RequestAbortedError(response.fallbackRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abortController.signal.removeEventListener("abort", abortHandler);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async pickCredential(cipherIds: string[]): Promise<string> {
|
async pickCredential(
|
||||||
|
cipherIds: string[],
|
||||||
|
abortController = new AbortController()
|
||||||
|
): Promise<string> {
|
||||||
const requestId = Utils.newGuid();
|
const requestId = Utils.newGuid();
|
||||||
const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId };
|
const data: BrowserFido2Message = { type: "PickCredentialRequest", cipherIds, requestId };
|
||||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
|
|
||||||
|
const abortHandler = () =>
|
||||||
|
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||||
|
abortController.signal.addEventListener("abort", abortHandler);
|
||||||
|
|
||||||
this.popupUtilsService.popOut(
|
this.popupUtilsService.popOut(
|
||||||
null,
|
null,
|
||||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
`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);
|
throw new RequestAbortedError(response.fallbackRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,10 +140,15 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
throw new RequestAbortedError();
|
throw new RequestAbortedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abortController.signal.removeEventListener("abort", abortHandler);
|
||||||
|
|
||||||
return response.cipherId;
|
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 requestId = Utils.newGuid();
|
||||||
const data: BrowserFido2Message = {
|
const data: BrowserFido2Message = {
|
||||||
type: "ConfirmNewCredentialRequest",
|
type: "ConfirmNewCredentialRequest",
|
||||||
@@ -125,6 +157,11 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
userName,
|
userName,
|
||||||
};
|
};
|
||||||
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
const queryParams = new URLSearchParams({ data: JSON.stringify(data) }).toString();
|
||||||
|
|
||||||
|
const abortHandler = () =>
|
||||||
|
BrowserFido2UserInterfaceService.sendMessage({ type: "AbortRequest", requestId });
|
||||||
|
abortController.signal.addEventListener("abort", abortHandler);
|
||||||
|
|
||||||
this.popupUtilsService.popOut(
|
this.popupUtilsService.popOut(
|
||||||
null,
|
null,
|
||||||
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
|
||||||
@@ -143,14 +180,12 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.type === "RequestCancelled") {
|
if (response.type === "AbortResponse") {
|
||||||
throw new RequestAbortedError(response.fallbackRequested);
|
throw new RequestAbortedError(response.fallbackRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abortController.signal.removeEventListener("abort", abortHandler);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private processMessage(msg: BrowserFido2Message) {
|
|
||||||
this.messages$.next(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ export interface NewCredentialParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Fido2UserInterfaceService {
|
export abstract class Fido2UserInterfaceService {
|
||||||
confirmCredential: (cipherId: string) => Promise<boolean>;
|
confirmCredential: (cipherId: string, abortController?: AbortController) => Promise<boolean>;
|
||||||
pickCredential: (cipherIds: string[]) => Promise<string>;
|
pickCredential: (cipherIds: string[], abortController?: AbortController) => Promise<string>;
|
||||||
confirmNewCredential: (params: NewCredentialParams) => Promise<boolean>;
|
confirmNewCredential: (
|
||||||
|
params: NewCredentialParams,
|
||||||
|
abortController?: AbortController
|
||||||
|
) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
params: CredentialRegistrationParams,
|
params: CredentialRegistrationParams,
|
||||||
abortController?: AbortController
|
abortController?: AbortController
|
||||||
): Promise<CredentialRegistrationResult> {
|
): Promise<CredentialRegistrationResult> {
|
||||||
const presence = await this.fido2UserInterfaceService.confirmNewCredential({
|
const presence = await this.fido2UserInterfaceService.confirmNewCredential(
|
||||||
credentialName: params.rp.name,
|
{
|
||||||
userName: params.user.displayName,
|
credentialName: params.rp.name,
|
||||||
});
|
userName: params.user.displayName,
|
||||||
|
},
|
||||||
|
abortController
|
||||||
|
);
|
||||||
|
|
||||||
const attestationFormat = STANDARD_ATTESTATION_FORMAT;
|
const attestationFormat = STANDARD_ATTESTATION_FORMAT;
|
||||||
const encoder = new TextEncoder();
|
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;
|
let credential: BitCredential | undefined;
|
||||||
|
|
||||||
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
|
||||||
@@ -138,7 +144,10 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
// throw new OriginMismatchError();
|
// throw new OriginMismatchError();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
await this.fido2UserInterfaceService.confirmCredential(credential.credentialId.encoded);
|
await this.fido2UserInterfaceService.confirmCredential(
|
||||||
|
credential.credentialId.encoded,
|
||||||
|
abortController
|
||||||
|
);
|
||||||
} 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);
|
||||||
@@ -148,7 +157,8 @@ export class Fido2Service implements Fido2ServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pickedId = await this.fido2UserInterfaceService.pickCredential(
|
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);
|
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;
|
let cipher: Cipher | undefined;
|
||||||
for (const allowedCredential of allowedCredentialIds) {
|
for (const allowedCredential of allowedCredentialIds) {
|
||||||
cipher = await this.cipherService.get(allowedCredential);
|
cipher = await this.cipherService.get(allowedCredential);
|
||||||
|
|||||||
Reference in New Issue
Block a user