1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-15 10:35:20 +01:00

Added lock and login redirect and added functionality to abort when in login or locked state

This commit is contained in:
gbubemismith 2023-07-31 18:01:58 -04:00
parent e6e088fd02
commit 85114105ac
No known key found for this signature in database
6 changed files with 123 additions and 55 deletions

View File

@ -528,7 +528,11 @@ export default class MainBackground {
);
this.popupUtilsService = new PopupUtilsService(this.isPrivateMode);
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService);
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(
this.popupUtilsService,
this.authService,
this.syncService
);
this.fido2AuthenticatorService = new Fido2AuthenticatorService(
this.cipherService,
this.fido2UserInterfaceService,

View File

@ -4,12 +4,18 @@ import {
filter,
firstValueFrom,
fromEvent,
merge,
Observable,
Subject,
switchMap,
take,
takeUntil,
throwError,
fromEventPattern,
} from "rxjs";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserRequestedFallbackAbortReason } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction";
import {
@ -18,6 +24,7 @@ import {
NewCredentialParams,
PickCredentialParams,
} from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { BrowserApi } from "../../platform/browser/browser-api";
import { Popout, PopupUtilsService } from "../../popup/services/popup-utils.service";
@ -105,7 +112,11 @@ export type BrowserFido2Message = { sessionId: string } & (
);
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
constructor(private popupUtilsService: PopupUtilsService) {}
constructor(
private popupUtilsService: PopupUtilsService,
private authService: AuthService,
private syncService: SyncService
) {}
async newSession(
fallbackSupported: boolean,
@ -113,6 +124,8 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
): Promise<Fido2UserInterfaceSession> {
return await BrowserFido2UserInterfaceSession.create(
this.popupUtilsService,
this.authService,
this.syncService,
fallbackSupported,
abortController
);
@ -122,11 +135,15 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSession {
static async create(
popupUtilsService: PopupUtilsService,
authService: AuthService,
syncService: SyncService,
fallbackSupported: boolean,
abortController?: AbortController
): Promise<BrowserFido2UserInterfaceSession> {
return new BrowserFido2UserInterfaceSession(
popupUtilsService,
authService,
syncService,
fallbackSupported,
abortController
);
@ -140,12 +157,16 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
private messages$ = (BrowserApi.messageListener$() as Observable<BrowserFido2Message>).pipe(
filter((msg) => msg.sessionId === this.sessionId)
);
private windowClosed$: Observable<number>;
private tabClosed$: Observable<number>;
private connected$ = new BehaviorSubject(false);
private destroy$ = new Subject<void>();
private popout?: Popout;
private constructor(
private readonly popupUtilsService: PopupUtilsService,
private readonly authService: AuthService,
private readonly syncService: SyncService,
private readonly fallbackSupported: boolean,
readonly abortController = new AbortController(),
readonly sessionId = Utils.newGuid()
@ -181,12 +202,20 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
.subscribe((msg) => {
if (msg.type === "AbortResponse") {
this.close();
this.abortController.abort(
msg.fallbackRequested ? UserRequestedFallbackAbortReason : undefined
);
this.abort(msg.fallbackRequested);
}
});
this.windowClosed$ = fromEventPattern(
(handler: any) => chrome.windows.onRemoved.addListener(handler),
(handler: any) => chrome.windows.onRemoved.removeListener(handler)
);
this.tabClosed$ = fromEventPattern(
(handler: any) => chrome.windows.onRemoved.addListener(handler),
(handler: any) => chrome.windows.onRemoved.removeListener(handler)
);
BrowserFido2UserInterfaceSession.sendMessage({
type: "NewSessionCreatedRequest",
sessionId,
@ -269,6 +298,15 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
await this.receive("AbortResponse");
}
async ensureUnlockedVault(): Promise<void> {
await this.connect();
// await this.syncService.fullSync(false);
}
async fullSync(): Promise<void> {
await this.syncService.fullSync(false);
}
async informCredentialNotFound(): Promise<void> {
const data: BrowserFido2Message = {
type: "InformCredentialNotFoundRequest",
@ -280,19 +318,6 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
await this.receive("AbortResponse");
}
async login(userVerification: boolean): Promise<{ userVerified: boolean }> {
const data: BrowserFido2Message = {
type: "LogInRequest",
userVerification,
sessionId: this.sessionId,
};
await this.send(data);
const response = await this.receive("LogInResponse");
return { userVerified: response.userVerified };
}
async close() {
this.popupUtilsService.closePopOut(this.popout);
this.closed = true;
@ -300,6 +325,10 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
this.destroy$.complete();
}
async abort(fallback = false) {
this.abortController.abort(fallback ? UserRequestedFallbackAbortReason : undefined);
}
private async send(msg: BrowserFido2Message): Promise<void> {
if (!this.connected$.value) {
await this.connect();
@ -331,16 +360,70 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
throw new Error("Cannot re-open closed session");
}
const queryParams = new URLSearchParams({ sessionId: this.sessionId }).toString();
// create promise first to avoid race condition where the popout opens before we start listening
const connectPromise = firstValueFrom(
this.connected$.pipe(filter((connected) => connected === true))
);
this.popout = await this.popupUtilsService.popOut(
null,
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
{ center: true }
merge(
this.connected$.pipe(filter((connected) => connected === true)),
fromEvent(this.abortController.signal, "abort").pipe(
switchMap(() => throwError(() => new SessionClosedError()))
)
)
);
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
const queryParams = new URLSearchParams({
sessionId: this.sessionId,
redirectPath: "fido2",
}).toString();
this.popout = await this.popupUtilsService.popOut(
null,
`popup/index.html?uilocation=popout#/home?${queryParams}`,
{ center: true }
);
} else if (authStatus === AuthenticationStatus.Locked) {
const queryParams = new URLSearchParams({
sessionId: this.sessionId,
redirectPath: "fido2",
}).toString();
this.popout = await this.popupUtilsService.popOut(
null,
`popup/index.html?uilocation=popout#/lock?${queryParams}`,
{ center: true }
);
} else {
const queryParams = new URLSearchParams({ sessionId: this.sessionId }).toString();
this.popout = await this.popupUtilsService.popOut(
null,
`popup/index.html?uilocation=popout#/fido2?${queryParams}`,
{ center: true }
);
}
if (this.popout.type === "window") {
const popoutWindow = this.popout as { type: "window"; window: chrome.windows.Window };
this.windowClosed$
.pipe(
filter((windowId) => popoutWindow.window.id === windowId),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.close();
this.abort();
});
} else if (this.popout.type === "tab") {
const popoutTab = this.popout as { type: "tab"; tab: chrome.tabs.Tab };
this.tabClosed$
.pipe(
filter((tabId) => popoutTab.tab.id === tabId),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.close();
this.abort();
});
}
await connectPromise;
}
}

View File

@ -16,14 +16,15 @@
A site is asking for authentication, please choose one of the following credentials to use:
<div class="box list">
<div class="box-content">
<app-cipher-row
<!-- <app-cipher-row
*ngFor="let cipher of ciphers"
[cipher]="cipher"
(onSelected)="pick(cipher)"
></app-cipher-row>
></app-cipher-row> -->
</div>
</div>
</ng-container>
<p>Here herer</p>
<ng-container *ngIf="data.message.type == 'ConfirmNewCredentialRequest'">
A site wants to create the following passkey in your vault
<div class="box list">

View File

@ -1,4 +1,4 @@
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import {
BehaviorSubject,
@ -19,7 +19,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { Fido2KeyView } from "@bitwarden/common/vault/models/view/fido2-key.view";
import { BrowserApi } from "../../../../platform/browser/browser-api";
import { PopupUtilsService } from "../../../../popup/services/popup-utils.service";
import {
BrowserFido2Message,
BrowserFido2UserInterfaceSession,
@ -49,8 +48,7 @@ export class Fido2Component implements OnInit, OnDestroy {
constructor(
private activatedRoute: ActivatedRoute,
private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService,
private popupUtils: PopupUtilsService
private passwordRepromptService: PasswordRepromptService
) {}
ngOnInit(): void {
@ -63,19 +61,6 @@ export class Fido2Component implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$))
.subscribe(([sessionId, message]) => {
this.sessionId = sessionId;
if (message.type === "LogInRequest") {
//route to login
const url = chrome.extension.getURL(
`popup/index.html#/home?sessionId=${sessionId}&redirectPath=fido2&uilocation=popout`
);
// BrowserApi.createNewWindow(url);
BrowserApi.createNewTab(url);
// if (this.popupUtils.inPopup(window)) {
// BrowserApi.closePopup(window);
// }
return;
}
if (message.type === "NewSessionCreatedRequest" && message.sessionId !== sessionId) {
return this.abort(false);
}
@ -196,7 +181,6 @@ export class Fido2Component implements OnInit, OnDestroy {
window.close();
}
@HostListener("window:unload")
unload(fallback = false) {
this.send({
sessionId: this.sessionId,

View File

@ -32,11 +32,12 @@ export abstract class Fido2UserInterfaceSession {
params: NewCredentialParams,
abortController?: AbortController
) => Promise<{ cipherId: string; userVerified: boolean }>;
ensureUnlockedVault: () => Promise<void>;
fullSync: () => Promise<void>;
informExcludedCredential: (
existingCipherIds: string[],
abortController?: AbortController
) => Promise<void>;
informCredentialNotFound: (abortController?: AbortController) => Promise<void>;
login: (userVerification: boolean) => Promise<{ userVerified: boolean }>;
close: () => void;
}

View File

@ -1,9 +1,9 @@
import { CBOR } from "cbor-redux";
import { AuthService } from "../../../auth/abstractions/auth.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { Utils } from "../../../platform/misc/utils";
import { CipherService } from "../../abstractions/cipher.service";
import { AuthService } from "../../../auth/abstractions/auth.service";
import {
Fido2AlgorithmIdentifier,
Fido2AutenticatorError,
@ -22,7 +22,6 @@ import { Fido2KeyView } from "../../models/view/fido2-key.view";
import { joseToDer } from "./ecdsa-utils";
import { Fido2Utils } from "./fido2-utils";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
// AAGUID: 6e8248d5-b479-40db-a3d8-11116f7e8349
export const AAGUID = new Uint8Array([
@ -221,14 +220,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
params.fallbackSupported,
abortController
);
try {
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.LoggedOut) {
const response = await userInterfaceSession.login(params.requireUserVerification);
}
if (
params.requireUserVerification != undefined &&
typeof params.requireUserVerification !== "boolean"
@ -243,6 +235,9 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
let cipherOptions: CipherView[];
//TODO: add here
await userInterfaceSession.ensureUnlockedVault();
// eslint-disable-next-line no-empty
if (params.allowCredentialDescriptorList?.length > 0) {
cipherOptions = await this.findCredentialsById(