mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-15 01:11:47 +01:00
PM-8113 - TwoFactorAuth + Webauthn - Refactor logic
This commit is contained in:
parent
a35ab8f2d2
commit
09f4a468c9
@ -20,7 +20,7 @@ export class ExtensionTwoFactorAuthComponentService
|
||||
super();
|
||||
}
|
||||
|
||||
shouldCheckForWebauthnResponseOnInit(): boolean {
|
||||
shouldCheckForWebAuthnQueryParamResponse(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,12 @@ export class ExtensionTwoFactorAuthWebAuthnComponentService
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* In the browser extension, we open webAuthn in a new web client tab sometimes due to inline
|
||||
* WebAuthn Iframe's not working in some browsers. We open a 2FA popout upon successful
|
||||
* completion of WebAuthn submission with query parameters to finish the 2FA process.
|
||||
* @returns boolean
|
||||
*/
|
||||
shouldOpenWebAuthnInNewTab(): boolean {
|
||||
const isChrome = this.platformUtilsService.isChrome();
|
||||
if (isChrome) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
export abstract class TwoFactorAuthWebAuthnComponentService {
|
||||
/**
|
||||
* Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab.
|
||||
* In a browser extension context, we open WebAuthn in a new web client tab.
|
||||
*/
|
||||
abstract shouldOpenWebAuthnInNewTab(): boolean;
|
||||
}
|
||||
|
@ -25,6 +25,11 @@ import {
|
||||
|
||||
import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service";
|
||||
|
||||
export interface WebAuthnResult {
|
||||
token: string;
|
||||
remember?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-webauthn",
|
||||
@ -44,7 +49,7 @@ import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauth
|
||||
providers: [],
|
||||
})
|
||||
export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
||||
@Output() token = new EventEmitter<string>();
|
||||
@Output() webAuthnResultEmitter = new EventEmitter<WebAuthnResult>();
|
||||
|
||||
webAuthnReady = false;
|
||||
webAuthnNewTab = false;
|
||||
@ -67,17 +72,26 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
||||
const webAuthnResponse = this.route.snapshot.paramMap.get("webAuthnResponse");
|
||||
|
||||
if (webAuthnResponse != null) {
|
||||
// TODO: determine if we even need this with the top level processing of the webauthn response.
|
||||
this.token.emit(webAuthnResponse);
|
||||
// TODO: should we early return?
|
||||
// return;
|
||||
}
|
||||
if (this.webAuthnNewTab && this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
||||
this.submitWebAuthnNewTabResponse();
|
||||
} else {
|
||||
await this.buildWebAuthnIFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private submitWebAuthnNewTabResponse() {
|
||||
const webAuthnNewTabResponse = this.route.snapshot.paramMap.get("webAuthnResponse");
|
||||
const remember = this.route.snapshot.queryParamMap.get("remember") === "true";
|
||||
|
||||
if (webAuthnNewTabResponse != null) {
|
||||
this.webAuthnResultEmitter.emit({
|
||||
token: webAuthnNewTabResponse,
|
||||
remember,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async buildWebAuthnIFrame() {
|
||||
if (this.win != null && this.webAuthnSupported) {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const webVaultUrl = env.getWebVaultUrl();
|
||||
@ -88,7 +102,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
||||
this.platformUtilsService,
|
||||
this.i18nService,
|
||||
(token: string) => {
|
||||
this.token.emit(token);
|
||||
this.webAuthnResultEmitter.emit({ token });
|
||||
},
|
||||
(error: string) => {
|
||||
this.toastService.showToast({
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
} from "./two-factor-auth-component.service";
|
||||
|
||||
export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService {
|
||||
shouldCheckForWebauthnResponseOnInit() {
|
||||
shouldCheckForWebAuthnQueryParamResponse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@ export enum LegacyKeyMigrationAction {
|
||||
export abstract class TwoFactorAuthComponentService {
|
||||
/**
|
||||
* Determines if the client should check for a webauthn response on init.
|
||||
* Currently, only the extension should check on init.
|
||||
* Currently, only the extension should check during component initialization.
|
||||
*/
|
||||
abstract shouldCheckForWebauthnResponseOnInit(): boolean;
|
||||
abstract shouldCheckForWebAuthnQueryParamResponse(): boolean;
|
||||
|
||||
/**
|
||||
* Extends the popup width if required.
|
||||
|
@ -19,7 +19,7 @@
|
||||
*ngIf="selectedProviderType === providerType.Yubikey"
|
||||
/>
|
||||
<app-two-factor-auth-webauthn
|
||||
(token)="token = $event; submitForm()"
|
||||
(webAuthnResultEmitter)="processWebAuthnResult($event)"
|
||||
*ngIf="selectedProviderType === providerType.WebAuthn"
|
||||
/>
|
||||
<app-two-factor-auth-duo
|
||||
|
@ -41,7 +41,10 @@ import {
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component";
|
||||
import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component";
|
||||
import {
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
WebAuthnResult,
|
||||
} from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component";
|
||||
import {
|
||||
LegacyKeyMigrationAction,
|
||||
@ -143,12 +146,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.listenFor2faSessionTimeout();
|
||||
|
||||
if (this.twoFactorAuthComponentService.shouldCheckForWebauthnResponseOnInit()) {
|
||||
await this.processWebAuthnResponseIfExists();
|
||||
// TODO: should we return here?
|
||||
// return;
|
||||
}
|
||||
|
||||
await this.setSelected2faProviderType();
|
||||
await this.set2faProviderData();
|
||||
await this.setTitleByTwoFactorProviderType();
|
||||
@ -170,19 +167,21 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
private async processWebAuthnResponseIfExists() {
|
||||
const webAuthn2faResponse = this.activatedRoute.snapshot.queryParamMap.get("webAuthnResponse");
|
||||
if (webAuthn2faResponse) {
|
||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||
await this.set2faProviderData();
|
||||
this.token = webAuthn2faResponse;
|
||||
this.remember = this.activatedRoute.snapshot.queryParamMap.get("remember") === "true";
|
||||
await this.submit();
|
||||
}
|
||||
}
|
||||
|
||||
private async setSelected2faProviderType() {
|
||||
const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win);
|
||||
|
||||
if (
|
||||
this.twoFactorAuthComponentService.shouldCheckForWebAuthnQueryParamResponse() &&
|
||||
webAuthnSupported
|
||||
) {
|
||||
const webAuthn2faResponse =
|
||||
this.activatedRoute.snapshot.queryParamMap.get("webAuthnResponse");
|
||||
if (webAuthn2faResponse) {
|
||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedProviderType = await this.twoFactorService.getDefaultProvider(webAuthnSupported);
|
||||
}
|
||||
|
||||
@ -212,6 +211,14 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
async processWebAuthnResult(webAuthnResponse: WebAuthnResult) {
|
||||
this.token = webAuthnResponse.token;
|
||||
if (webAuthnResponse.remember) {
|
||||
this.remember = webAuthnResponse.remember;
|
||||
}
|
||||
await this.submit();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.token == null || this.token === "") {
|
||||
this.toastService.showToast({
|
||||
|
Loading…
Reference in New Issue
Block a user