1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-16 10:45: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.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.fido2AuthenticatorService = new Fido2AuthenticatorService(
this.cipherService, this.cipherService,
this.fido2UserInterfaceService, this.fido2UserInterfaceService,

View File

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

View File

@ -16,14 +16,15 @@
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
*ngFor="let cipher of ciphers" *ngFor="let cipher of ciphers"
[cipher]="cipher" [cipher]="cipher"
(onSelected)="pick(cipher)" (onSelected)="pick(cipher)"
></app-cipher-row> ></app-cipher-row> -->
</div> </div>
</div> </div>
</ng-container> </ng-container>
<p>Here herer</p>
<ng-container *ngIf="data.message.type == 'ConfirmNewCredentialRequest'"> <ng-container *ngIf="data.message.type == 'ConfirmNewCredentialRequest'">
A site wants to create the following passkey in your vault A site wants to create the following passkey in your vault
<div class="box list"> <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 { ActivatedRoute } from "@angular/router";
import { import {
BehaviorSubject, 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 { Fido2KeyView } from "@bitwarden/common/vault/models/view/fido2-key.view";
import { BrowserApi } from "../../../../platform/browser/browser-api"; import { BrowserApi } from "../../../../platform/browser/browser-api";
import { PopupUtilsService } from "../../../../popup/services/popup-utils.service";
import { import {
BrowserFido2Message, BrowserFido2Message,
BrowserFido2UserInterfaceSession, BrowserFido2UserInterfaceSession,
@ -49,8 +48,7 @@ export class Fido2Component implements OnInit, OnDestroy {
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private cipherService: CipherService, private cipherService: CipherService,
private passwordRepromptService: PasswordRepromptService, private passwordRepromptService: PasswordRepromptService
private popupUtils: PopupUtilsService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
@ -63,19 +61,6 @@ export class Fido2Component implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(([sessionId, message]) => { .subscribe(([sessionId, message]) => {
this.sessionId = sessionId; 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) { if (message.type === "NewSessionCreatedRequest" && message.sessionId !== sessionId) {
return this.abort(false); return this.abort(false);
} }
@ -196,7 +181,6 @@ export class Fido2Component implements OnInit, OnDestroy {
window.close(); window.close();
} }
@HostListener("window:unload")
unload(fallback = false) { unload(fallback = false) {
this.send({ this.send({
sessionId: this.sessionId, sessionId: this.sessionId,

View File

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

View File

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