1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

feat(new-device-verification-screen): (Auth) [PM-17489] Back Button on New Device Verification Screen (#16599)

On Web and Desktop, show back button on `NewDeviceVerificationComponent` (route `/device-verification`). Do not show it on Extension, because Extension already has a back button in the header.
This commit is contained in:
rr-bw
2025-10-01 12:57:41 -07:00
committed by GitHub
parent 420b26776b
commit cae58232e5
10 changed files with 109 additions and 4 deletions

View File

@@ -0,0 +1,21 @@
import { ExtensionNewDeviceVerificationComponentService } from "./extension-new-device-verification-component.service";
describe("ExtensionNewDeviceVerificationComponentService", () => {
let sut: ExtensionNewDeviceVerificationComponentService;
beforeEach(() => {
sut = new ExtensionNewDeviceVerificationComponentService();
});
it("should instantiate the service", () => {
expect(sut).not.toBeFalsy();
});
describe("showBackButton()", () => {
it("should return false", () => {
const result = sut.showBackButton();
expect(result).toBe(false);
});
});
});

View File

@@ -0,0 +1,13 @@
import {
DefaultNewDeviceVerificationComponentService,
NewDeviceVerificationComponentService,
} from "@bitwarden/auth/angular";
export class ExtensionNewDeviceVerificationComponentService
extends DefaultNewDeviceVerificationComponentService
implements NewDeviceVerificationComponentService
{
showBackButton() {
return false;
}
}

View File

@@ -29,6 +29,7 @@ import {
TwoFactorAuthDuoComponentService, TwoFactorAuthDuoComponentService,
TwoFactorAuthWebAuthnComponentService, TwoFactorAuthWebAuthnComponentService,
SsoComponentService, SsoComponentService,
NewDeviceVerificationComponentService,
} from "@bitwarden/auth/angular"; } from "@bitwarden/auth/angular";
import { import {
LockService, LockService,
@@ -36,6 +37,7 @@ import {
SsoUrlService, SsoUrlService,
LogoutService, LogoutService,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ExtensionNewDeviceVerificationComponentService } from "@bitwarden/browser/auth/services/new-device-verification/extension-new-device-verification-component.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -710,6 +712,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultCipherArchiveService, useClass: DefaultCipherArchiveService,
deps: [CipherService, ApiService, BillingAccountProfileStateService, ConfigService], deps: [CipherService, ApiService, BillingAccountProfileStateService, ConfigService],
}), }),
safeProvider({
provide: NewDeviceVerificationComponentService,
useClass: ExtensionNewDeviceVerificationComponentService,
deps: [],
}),
]; ];
@NgModule({ @NgModule({

View File

@@ -20,11 +20,13 @@ import {
import { import {
DefaultLoginComponentService, DefaultLoginComponentService,
DefaultLoginDecryptionOptionsService, DefaultLoginDecryptionOptionsService,
DefaultNewDeviceVerificationComponentService,
DefaultRegistrationFinishService, DefaultRegistrationFinishService,
DefaultTwoFactorAuthComponentService, DefaultTwoFactorAuthComponentService,
DefaultTwoFactorAuthWebAuthnComponentService, DefaultTwoFactorAuthWebAuthnComponentService,
LoginComponentService, LoginComponentService,
LoginDecryptionOptionsService, LoginDecryptionOptionsService,
NewDeviceVerificationComponentService,
RegistrationFinishService as RegistrationFinishServiceAbstraction, RegistrationFinishService as RegistrationFinishServiceAbstraction,
TwoFactorAuthComponentService, TwoFactorAuthComponentService,
TwoFactorAuthWebAuthnComponentService, TwoFactorAuthWebAuthnComponentService,
@@ -1646,6 +1648,11 @@ const safeProviders: SafeProvider[] = [
ConfigService, ConfigService,
], ],
}), }),
safeProvider({
provide: NewDeviceVerificationComponentService,
useClass: DefaultNewDeviceVerificationComponentService,
deps: [],
}),
]; ];
@NgModule({ @NgModule({

View File

@@ -59,6 +59,8 @@ export * from "./two-factor-auth";
// device verification // device verification
export * from "./new-device-verification/new-device-verification.component"; export * from "./new-device-verification/new-device-verification.component";
export * from "./new-device-verification/new-device-verification-component.service";
export * from "./new-device-verification/default-new-device-verification-component.service";
// validators // validators
export * from "./validators/compare-inputs.validator"; export * from "./validators/compare-inputs.validator";

View File

@@ -0,0 +1,21 @@
import { DefaultNewDeviceVerificationComponentService } from "./default-new-device-verification-component.service";
describe("DefaultNewDeviceVerificationComponentService", () => {
let sut: DefaultNewDeviceVerificationComponentService;
beforeEach(() => {
sut = new DefaultNewDeviceVerificationComponentService();
});
it("should instantiate the service", () => {
expect(sut).not.toBeFalsy();
});
describe("showBackButton()", () => {
it("should return true", () => {
const result = sut.showBackButton();
expect(result).toBe(true);
});
});
});

View File

@@ -0,0 +1,9 @@
import { NewDeviceVerificationComponentService } from "./new-device-verification-component.service";
export class DefaultNewDeviceVerificationComponentService
implements NewDeviceVerificationComponentService
{
showBackButton() {
return true;
}
}

View File

@@ -0,0 +1,8 @@
export abstract class NewDeviceVerificationComponentService {
/**
* States whether component should show a back button. Can be overridden by client-specific component services.
* - Default = `true`
* - Extension = `false` (because Extension shows a back button in the header instead)
*/
abstract showBackButton: () => boolean;
}

View File

@@ -22,7 +22,7 @@
{{ "resendCode" | i18n }} {{ "resendCode" | i18n }}
</button> </button>
<div class="tw-flex tw-mt-4"> <div class="tw-grid tw-gap-3 tw-mt-4">
<button <button
bitButton bitButton
bitFormButton bitFormButton
@@ -33,5 +33,13 @@
> >
{{ "continueLoggingIn" | i18n }} {{ "continueLoggingIn" | i18n }}
</button> </button>
@if (showBackButton) {
<div class="tw-text-center">{{ "or" | i18n }}</div>
<button type="button" bitButton block buttonType="secondary" (click)="goBack()">
{{ "back" | i18n }}
</button>
}
</div> </div>
</form> </form>

View File

@@ -1,4 +1,4 @@
import { CommonModule } from "@angular/common"; import { CommonModule, Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
@@ -11,7 +11,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
@@ -26,6 +25,8 @@ import {
import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login-strategy.service"; import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login-strategy.service";
import { NewDeviceVerificationComponentService } from "./new-device-verification-component.service";
/** /**
* Component for verifying a new device via a one-time password (OTP). * Component for verifying a new device via a one-time password (OTP).
*/ */
@@ -57,6 +58,7 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
protected disableRequestOTP = false; protected disableRequestOTP = false;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
protected authenticationSessionTimeoutRoute = "/authentication-timeout"; protected authenticationSessionTimeoutRoute = "/authentication-timeout";
protected showBackButton = true;
constructor( constructor(
private router: Router, private router: Router,
@@ -66,12 +68,15 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
private logService: LogService, private logService: LogService,
private i18nService: I18nService, private i18nService: I18nService,
private loginSuccessHandlerService: LoginSuccessHandlerService, private loginSuccessHandlerService: LoginSuccessHandlerService,
private configService: ConfigService,
private accountService: AccountService, private accountService: AccountService,
private masterPasswordService: MasterPasswordServiceAbstraction, private masterPasswordService: MasterPasswordServiceAbstraction,
private newDeviceVerificationComponentService: NewDeviceVerificationComponentService,
private location: Location,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.showBackButton = this.newDeviceVerificationComponentService.showBackButton();
// Redirect to timeout route if session expires // Redirect to timeout route if session expires
this.loginStrategyService.authenticationSessionTimeout$ this.loginStrategyService.authenticationSessionTimeout$
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
@@ -179,4 +184,8 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
codeControl.markAsTouched(); codeControl.markAsTouched();
} }
}; };
protected goBack() {
this.location.back();
}
} }