diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts new file mode 100644 index 0000000000..b59099470d --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts @@ -0,0 +1,27 @@ +import { filter, map, Observable } from "rxjs"; + +import { Duo2faResult, TwoFactorAuthDuoComponentService } from "@bitwarden/auth/angular"; + +import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; + +interface Message { + command: string; + code: string; + state: string; +} + +export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { + constructor(private browserMessagingApi: ZonedMessageListenerService) {} + listenForDuo2faResult$(): Observable { + return this.browserMessagingApi.messageListener$().pipe( + filter((msg: Message) => msg.command === "duoResult"), + map((msg: Message) => { + return { + code: msg.code, + state: msg.state, + token: `${msg.code}|${msg.state}`, + } as Duo2faResult; + }), + ); + } +} diff --git a/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts new file mode 100644 index 0000000000..37e044a7a3 --- /dev/null +++ b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts @@ -0,0 +1,28 @@ +import { map, Observable } from "rxjs"; + +import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular"; +import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; + +// TODO: PM-16209 We should create a Duo2faMessageListenerService that listens for messages from duo +// and this command definition should move to that file. +// We should explore consolidating the messaging approach across clients - i.e., we +// should use the same command definition across all clients. We use duoResult on extension for no real +// benefit. +export const DUO_2FA_RESULT_COMMAND = new CommandDefinition<{ code: string; state: string }>( + "duoCallback", +); + +export class DesktopTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { + constructor(private messageListener: MessageListener) {} + listenForDuo2faResult$(): Observable { + return this.messageListener.messages$(DUO_2FA_RESULT_COMMAND).pipe( + map((msg) => { + return { + code: msg.code, + state: msg.state, + token: `${msg.code}|${msg.state}`, + } as Duo2faResult; + }), + ); + } +} diff --git a/apps/web/src/app/auth/core/services/web-two-factor-auth-duo-component.service.ts b/apps/web/src/app/auth/core/services/web-two-factor-auth-duo-component.service.ts new file mode 100644 index 0000000000..15c65aa6fc --- /dev/null +++ b/apps/web/src/app/auth/core/services/web-two-factor-auth-duo-component.service.ts @@ -0,0 +1,26 @@ +import { fromEvent, map, Observable, share } from "rxjs"; + +import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular"; + +export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { + private duo2faResult$: Observable; + + constructor() { + const duoResultChannel: BroadcastChannel = new BroadcastChannel("duoResult"); + + this.duo2faResult$ = fromEvent(duoResultChannel, "message").pipe( + map((msg: MessageEvent) => { + return { + code: msg.data.code, + state: msg.data.state, + token: `${msg.data.code}|${msg.data.state}`, + } as Duo2faResult; + }), + // share the observable so that multiple subscribers can listen to the same event + share(), + ); + } + listenForDuo2faResult$(): Observable { + return this.duo2faResult$; + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/index.ts index 47ab5b1912..de8bfa5958 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/index.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/index.ts @@ -1 +1,2 @@ export * from "./two-factor-auth-email"; +export * from "./two-factor-auth-duo"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts index e69de29bb2..c43325e0d0 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/index.ts @@ -0,0 +1 @@ +export * from "./two-factor-auth-duo-component.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts new file mode 100644 index 0000000000..fc862e8672 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo-component.service.ts @@ -0,0 +1,21 @@ +import { Observable } from "rxjs"; + +export interface Duo2faResult { + code: string; + state: string; + /** + * The code and the state joined by a | character. + */ + token: string; +} + +/** + * A service which manages all the cross client logic for the duo 2FA component. + */ +export abstract class TwoFactorAuthDuoComponentService { + /** + * Retrieves the result of the duo two-factor authentication process. + * @returns {Observable} An observable that emits the result of the duo two-factor authentication process. + */ + abstract listenForDuo2faResult$(): Observable; +}