1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-14 01:01:31 +01:00

PM-8113 - TwoFactorAuthDuoCompService - add client specific handling for launchDuoFrameless

This commit is contained in:
Jared Snider 2024-12-19 16:54:36 -05:00
parent 9905c6fa0e
commit 5e9249a8cb
No known key found for this signature in database
GPG Key ID: A149DDD612516286
5 changed files with 87 additions and 19 deletions

View File

@ -1,6 +1,9 @@
import { filter, map, Observable } from "rxjs";
import { filter, firstValueFrom, map, Observable } from "rxjs";
import { Duo2faResult, TwoFactorAuthDuoComponentService } from "@bitwarden/auth/angular";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service";
@ -11,7 +14,12 @@ interface Message {
}
export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService {
constructor(private browserMessagingApi: ZonedMessageListenerService) {}
constructor(
private browserMessagingApi: ZonedMessageListenerService,
private environmentService: EnvironmentService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
) {}
listenForDuo2faResult$(): Observable<Duo2faResult> {
return this.browserMessagingApi.messageListener$().pipe(
filter((msg: Message) => msg.command === "duoResult"),
@ -24,4 +32,24 @@ export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthD
}),
);
}
async launchDuoFrameless(duoFramelessUrl: string): Promise<void> {
const duoHandOffMessage = {
title: this.i18nService.t("youSuccessfullyLoggedIn"),
message: this.i18nService.t("youMayCloseThisWindow"),
isCountdown: false,
};
// we're using the connector here as a way to set a cookie with translations
// before continuing to the duo frameless url
const env = await firstValueFrom(this.environmentService.environment$);
const launchUrl =
env.getWebVaultUrl() +
"/duo-redirect-connector.html" +
"?duoFramelessUrl=" +
encodeURIComponent(duoFramelessUrl) +
"&handOffMessage=" +
encodeURIComponent(JSON.stringify(duoHandOffMessage));
this.platformUtilsService.launchUri(launchUrl);
}
}

View File

@ -1,6 +1,9 @@
import { map, Observable } from "rxjs";
import { firstValueFrom, map, Observable } from "rxjs";
import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
// TODO: PM-16209 We should create a Duo2faMessageListenerService that listens for messages from duo
@ -13,7 +16,12 @@ export const DUO_2FA_RESULT_COMMAND = new CommandDefinition<{ code: string; stat
);
export class DesktopTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService {
constructor(private messageListener: MessageListener) {}
constructor(
private messageListener: MessageListener,
private environmentService: EnvironmentService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
) {}
listenForDuo2faResult$(): Observable<Duo2faResult> {
return this.messageListener.messages$(DUO_2FA_RESULT_COMMAND).pipe(
map((msg) => {
@ -25,4 +33,24 @@ export class DesktopTwoFactorAuthDuoComponentService implements TwoFactorAuthDuo
}),
);
}
async launchDuoFrameless(duoFramelessUrl: string): Promise<void> {
const duoHandOffMessage = {
title: this.i18nService.t("youSuccessfullyLoggedIn"),
message: this.i18nService.t("youMayCloseThisWindow"),
isCountdown: false,
};
// we're using the connector here as a way to set a cookie with translations
// before continuing to the duo frameless url
const env = await firstValueFrom(this.environmentService.environment$);
const launchUrl =
env.getWebVaultUrl() +
"/duo-redirect-connector.html" +
"?duoFramelessUrl=" +
encodeURIComponent(duoFramelessUrl) +
"&handOffMessage=" +
encodeURIComponent(JSON.stringify(duoHandOffMessage));
this.platformUtilsService.launchUri(launchUrl);
}
}

View File

@ -1,11 +1,12 @@
import { fromEvent, map, Observable, share } from "rxjs";
import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService {
private duo2faResult$: Observable<Duo2faResult>;
constructor() {
constructor(private platformUtilsService: PlatformUtilsService) {
const duoResultChannel: BroadcastChannel = new BroadcastChannel("duoResult");
this.duo2faResult$ = fromEvent(duoResultChannel, "message").pipe(
@ -23,4 +24,8 @@ export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComp
listenForDuo2faResult$(): Observable<Duo2faResult> {
return this.duo2faResult$;
}
async launchDuoFrameless(duoFramelessUrl: string): Promise<void> {
this.platformUtilsService.launchUri(duoFramelessUrl);
}
}

View File

@ -18,4 +18,9 @@ export abstract class TwoFactorAuthDuoComponentService {
* @returns {Observable<Duo2faResult>} An observable that emits the result of the duo two-factor authentication process.
*/
abstract listenForDuo2faResult$(): Observable<Duo2faResult>;
/**
* Launches the client specific duo frameless 2FA flow.
*/
abstract launchDuoFrameless(duoFramelessUrl: string): Promise<void>;
}

View File

@ -3,6 +3,7 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -18,6 +19,11 @@ import {
ToastService,
} from "@bitwarden/components";
import {
Duo2faResult,
TwoFactorAuthDuoComponentService,
} from "./two-factor-auth-duo-component.service";
@Component({
standalone: true,
selector: "app-two-factor-auth-duo",
@ -41,32 +47,27 @@ export class TwoFactorAuthDuoComponent implements OnInit {
@Input() providerData: any;
duoFramelessUrl: string = null;
duoResultListenerInitialized = false;
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected toastService: ToastService,
private twoFactorAuthDuoComponentService: TwoFactorAuthDuoComponentService,
) {}
async ngOnInit(): Promise<void> {
await this.init();
}
async init() {
// Setup listener for duo-redirect.ts connector to send back the code
if (!this.duoResultListenerInitialized) {
// setup client specific duo result listener
this.setupDuoResultListener();
this.duoResultListenerInitialized = true;
}
this.twoFactorAuthDuoComponentService
.listenForDuo2faResult$()
.pipe(takeUntilDestroyed())
.subscribe((duo2faResult: Duo2faResult) => {
this.token.emit(duo2faResult.token);
});
// flow must be launched by user so they can choose to remember the device or not.
this.duoFramelessUrl = this.providerData.AuthUrl;
}
// Each client will have own implementation
protected setupDuoResultListener(): void {}
// Called via parent two-factor-auth component.
async launchDuoFrameless(): Promise<void> {
if (this.duoFramelessUrl === null) {
this.toastService.showToast({
@ -76,6 +77,7 @@ export class TwoFactorAuthDuoComponent implements OnInit {
});
return;
}
this.platformUtilsService.launchUri(this.duoFramelessUrl);
await this.twoFactorAuthDuoComponentService.launchDuoFrameless(this.duoFramelessUrl);
}
}