diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 21e8f9f208..ce944db78f 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -17,6 +17,7 @@ import { PinService, PinServiceAbstraction, UserDecryptionOptionsService, + Executor, } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -614,6 +615,11 @@ export class ServiceContainer { this.configService, ); + // Execute any authn session timeout logic without any wrapping logic. + // An executor is required to ensure the logic is executed in an Angular context when it + // it is available. + const authnSessionTimeoutExecutor: Executor = (fn) => fn(); + this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, @@ -640,6 +646,7 @@ export class ServiceContainer { this.vaultTimeoutSettingsService, this.kdfConfigService, this.taskSchedulerService, + authnSessionTimeoutExecutor, ); // FIXME: CLI does not support autofill diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 86c5642a0c..69a1ed3f61 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,7 +1,7 @@ import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; -import { LogoutReason } from "@bitwarden/auth/common"; +import { Executor, LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { @@ -68,3 +68,7 @@ export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken( "ENV_ADDITIONAL_REGIONS", ); + +export const AUTHN_SESSION_TIMEOUT_EXECUTOR = new SafeInjectionToken( + "AuthnSessionTimeoutExecutor", +); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a43f1fa07a..8637e26a2b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,4 +1,4 @@ -import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; +import { ErrorHandler, LOCALE_ID, NgModule, NgZone } from "@angular/core"; import { Subject } from "rxjs"; import { @@ -319,6 +319,7 @@ import { CLIENT_TYPE, REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, ENV_ADDITIONAL_REGIONS, + AUTHN_SESSION_TIMEOUT_EXECUTOR, } from "./injection-tokens"; import { ModalService } from "./modal.service"; @@ -411,6 +412,11 @@ const safeProviders: SafeProvider[] = [ TokenServiceAbstraction, ], }), + safeProvider({ + provide: AUTHN_SESSION_TIMEOUT_EXECUTOR, + useFactory: (ngZone: NgZone) => (fn: () => void) => ngZone.run(fn), + deps: [NgZone], + }), safeProvider({ provide: LoginStrategyServiceAbstraction, useClass: LoginStrategyService, @@ -440,6 +446,7 @@ const safeProviders: SafeProvider[] = [ VaultTimeoutSettingsServiceAbstraction, KdfConfigService, TaskSchedulerService, + AUTHN_SESSION_TIMEOUT_EXECUTOR, ], }), safeProvider({ diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 99e3c057e1..1d5001f1f0 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -71,6 +71,8 @@ import { const sessionTimeoutLength = 5 * 60 * 1000; // 5 minutes +export type Executor = (fn: () => void) => void; + export class LoginStrategyService implements LoginStrategyServiceAbstraction { private sessionTimeoutSubscription: Subscription; private currentAuthnTypeState: GlobalState; @@ -118,6 +120,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected kdfConfigService: KdfConfigService, protected taskSchedulerService: TaskSchedulerService, + private authnSessionTimeoutExecutor: Executor = (fn) => fn(), // Default to no-op ) { this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); @@ -128,12 +131,14 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, async () => { - this.twoFactorTimeoutSubject.next(true); - try { - await this.clearCache(); - } catch (e) { - this.logService.error("Failed to clear cache during session timeout", e); - } + this.authnSessionTimeoutExecutor(async () => { + this.twoFactorTimeoutSubject.next(true); + try { + await this.clearCache(); + } catch (e) { + this.logService.error("Failed to clear cache during session timeout", e); + } + }); }, );