mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-19 01:51:27 +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();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldCheckForWebauthnResponseOnInit(): boolean {
|
shouldCheckForWebAuthnQueryParamResponse(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,12 @@ export class ExtensionTwoFactorAuthWebAuthnComponentService
|
|||||||
super();
|
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 {
|
shouldOpenWebAuthnInNewTab(): boolean {
|
||||||
const isChrome = this.platformUtilsService.isChrome();
|
const isChrome = this.platformUtilsService.isChrome();
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
export abstract class TwoFactorAuthWebAuthnComponentService {
|
export abstract class TwoFactorAuthWebAuthnComponentService {
|
||||||
/**
|
/**
|
||||||
* Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab.
|
* 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;
|
abstract shouldOpenWebAuthnInNewTab(): boolean;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,11 @@ import {
|
|||||||
|
|
||||||
import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service";
|
import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service";
|
||||||
|
|
||||||
|
export interface WebAuthnResult {
|
||||||
|
token: string;
|
||||||
|
remember?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
selector: "app-two-factor-auth-webauthn",
|
selector: "app-two-factor-auth-webauthn",
|
||||||
@ -44,7 +49,7 @@ import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauth
|
|||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
||||||
@Output() token = new EventEmitter<string>();
|
@Output() webAuthnResultEmitter = new EventEmitter<WebAuthnResult>();
|
||||||
|
|
||||||
webAuthnReady = false;
|
webAuthnReady = false;
|
||||||
webAuthnNewTab = false;
|
webAuthnNewTab = false;
|
||||||
@ -67,17 +72,26 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
if (this.webAuthnNewTab && this.route.snapshot.paramMap.has("webAuthnResponse")) {
|
||||||
const webAuthnResponse = this.route.snapshot.paramMap.get("webAuthnResponse");
|
this.submitWebAuthnNewTabResponse();
|
||||||
|
} else {
|
||||||
if (webAuthnResponse != null) {
|
await this.buildWebAuthnIFrame();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (this.win != null && this.webAuthnSupported) {
|
||||||
const env = await firstValueFrom(this.environmentService.environment$);
|
const env = await firstValueFrom(this.environmentService.environment$);
|
||||||
const webVaultUrl = env.getWebVaultUrl();
|
const webVaultUrl = env.getWebVaultUrl();
|
||||||
@ -88,7 +102,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy {
|
|||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
(token: string) => {
|
(token: string) => {
|
||||||
this.token.emit(token);
|
this.webAuthnResultEmitter.emit({ token });
|
||||||
},
|
},
|
||||||
(error: string) => {
|
(error: string) => {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
} from "./two-factor-auth-component.service";
|
} from "./two-factor-auth-component.service";
|
||||||
|
|
||||||
export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService {
|
export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService {
|
||||||
shouldCheckForWebauthnResponseOnInit() {
|
shouldCheckForWebAuthnQueryParamResponse() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ export enum LegacyKeyMigrationAction {
|
|||||||
export abstract class TwoFactorAuthComponentService {
|
export abstract class TwoFactorAuthComponentService {
|
||||||
/**
|
/**
|
||||||
* Determines if the client should check for a webauthn response on init.
|
* 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.
|
* Extends the popup width if required.
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*ngIf="selectedProviderType === providerType.Yubikey"
|
*ngIf="selectedProviderType === providerType.Yubikey"
|
||||||
/>
|
/>
|
||||||
<app-two-factor-auth-webauthn
|
<app-two-factor-auth-webauthn
|
||||||
(token)="token = $event; submitForm()"
|
(webAuthnResultEmitter)="processWebAuthnResult($event)"
|
||||||
*ngIf="selectedProviderType === providerType.WebAuthn"
|
*ngIf="selectedProviderType === providerType.WebAuthn"
|
||||||
/>
|
/>
|
||||||
<app-two-factor-auth-duo
|
<app-two-factor-auth-duo
|
||||||
|
@ -41,7 +41,10 @@ import {
|
|||||||
import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component";
|
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 { 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 { 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 { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component";
|
||||||
import {
|
import {
|
||||||
LegacyKeyMigrationAction,
|
LegacyKeyMigrationAction,
|
||||||
@ -143,12 +146,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.listenFor2faSessionTimeout();
|
this.listenFor2faSessionTimeout();
|
||||||
|
|
||||||
if (this.twoFactorAuthComponentService.shouldCheckForWebauthnResponseOnInit()) {
|
|
||||||
await this.processWebAuthnResponseIfExists();
|
|
||||||
// TODO: should we return here?
|
|
||||||
// return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.setSelected2faProviderType();
|
await this.setSelected2faProviderType();
|
||||||
await this.set2faProviderData();
|
await this.set2faProviderData();
|
||||||
await this.setTitleByTwoFactorProviderType();
|
await this.setTitleByTwoFactorProviderType();
|
||||||
@ -170,19 +167,21 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processWebAuthnResponseIfExists() {
|
private async setSelected2faProviderType() {
|
||||||
const webAuthn2faResponse = this.activatedRoute.snapshot.queryParamMap.get("webAuthnResponse");
|
const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.twoFactorAuthComponentService.shouldCheckForWebAuthnQueryParamResponse() &&
|
||||||
|
webAuthnSupported
|
||||||
|
) {
|
||||||
|
const webAuthn2faResponse =
|
||||||
|
this.activatedRoute.snapshot.queryParamMap.get("webAuthnResponse");
|
||||||
if (webAuthn2faResponse) {
|
if (webAuthn2faResponse) {
|
||||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||||
await this.set2faProviderData();
|
return;
|
||||||
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);
|
|
||||||
this.selectedProviderType = await this.twoFactorService.getDefaultProvider(webAuthnSupported);
|
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() {
|
async submit() {
|
||||||
if (this.token == null || this.token === "") {
|
if (this.token == null || this.token === "") {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
|
Loading…
Reference in New Issue
Block a user