1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

feat(auth): [PM-13659] implement 2FA timeout handling across clients

Add timeout state management for two-factor authentication flows in web, desktop,
and browser extension clients. Includes:

- New timeout screen component with 5-minute session limit
- Updated UI elements and styling
- Comprehensive test coverage

Refs: PM-13659
This commit is contained in:
Alec Rippberger
2024-12-03 13:55:40 -06:00
committed by GitHub
parent 2e53a645c9
commit c073e91f17
13 changed files with 165 additions and 4 deletions

View File

@@ -0,0 +1,25 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ButtonModule } from "@bitwarden/components";
/**
* This component is used to display a message to the user that their authentication session has expired.
* It provides a button to navigate to the login page.
*/
@Component({
selector: "app-two-factor-expired",
standalone: true,
imports: [CommonModule, JslibModule, ButtonModule, RouterModule],
template: `
<p class="tw-text-center">
{{ "authenticationSessionTimedOut" | i18n }}
</p>
<a routerLink="/login" bitButton block buttonType="primary">
{{ "logIn" | i18n }}
</a>
`,
})
export class TwoFactorTimeoutComponent {}

View File

@@ -86,9 +86,12 @@ describe("TwoFactorComponent", () => {
};
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
let twoFactorTimeoutSubject: BehaviorSubject<boolean>;
beforeEach(() => {
twoFactorTimeoutSubject = new BehaviorSubject<boolean>(false);
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
mockLoginStrategyService.twoFactorTimeout$ = twoFactorTimeoutSubject;
mockRouter = mock<Router>();
mockI18nService = mock<I18nService>();
mockApiService = mock<ApiService>();
@@ -492,4 +495,10 @@ describe("TwoFactorComponent", () => {
});
});
});
it("navigates to the timeout route when timeout expires", async () => {
twoFactorTimeoutSubject.next(true);
expect(mockRouter.navigate).toHaveBeenCalledWith(["2fa-timeout"]);
});
});

View File

@@ -1,4 +1,5 @@
import { Directive, Inject, OnDestroy, OnInit } from "@angular/core";
import { Directive, Inject, OnInit, OnDestroy } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { first } from "rxjs/operators";
@@ -68,6 +69,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected changePasswordRoute = "set-password";
protected forcePasswordResetRoute = "update-temp-password";
protected successRoute = "vault";
protected twoFactorTimeoutRoute = "2fa-timeout";
get isDuoProvider(): boolean {
return (
@@ -99,6 +101,21 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
) {
super(environmentService, i18nService, platformUtilsService, toastService);
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
// Add subscription to twoFactorTimeout$ and navigate to twoFactorTimeoutRoute if expired
this.loginStrategyService.twoFactorTimeout$
.pipe(takeUntilDestroyed())
.subscribe(async (expired) => {
if (!expired) {
return;
}
try {
await this.router.navigate([this.twoFactorTimeoutRoute]);
} catch (err) {
this.logService.error(`Failed to navigate to ${this.twoFactorTimeoutRoute} route`, err);
}
});
}
async ngOnInit() {