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

feat(auth): [PM-9693] Refresh LoginDecryptionOptionsComponent (#11782)

Creates a refreshed and consolidated `LoginDecryptionOptionsComponent` for use on all visual clients, which will be used when the `UnauthenticatedExtensionUIRefresh` feature flag is on.
This commit is contained in:
rr-bw
2024-11-21 13:31:20 -08:00
committed by GitHub
parent 228817b85f
commit 9f99454b37
31 changed files with 742 additions and 38 deletions

View File

@@ -3287,9 +3287,18 @@
"opensInANewWindow": {
"message": "Opens in a new window"
},
"rememberThisDeviceToMakeFutureLoginsSeamless": {
"message": "Remember this device to make future logins seamless"
},
"deviceApprovalRequired": {
"message": "Device approval required. Select an approval option below:"
},
"deviceApprovalRequiredV2": {
"message": "Device approval required"
},
"selectAnApprovalOptionBelow": {
"message": "Select an approval option below"
},
"rememberThisDevice": {
"message": "Remember this device"
},
@@ -3363,6 +3372,9 @@
"userEmailMissing": {
"message": "User email missing"
},
"activeUserEmailNotFoundLoggingYouOut": {
"message": "Active user email not found. Logging you out."
},
"deviceTrusted": {
"message": "Device trusted"
},
@@ -3799,6 +3811,9 @@
"accessing": {
"message": "Accessing"
},
"loggedInExclamation": {
"message": "Logged in!"
},
"passkeyNotCopied": {
"message": "Passkey will not be copied"
},

View File

@@ -0,0 +1,64 @@
import { Router } from "@angular/router";
import { MockProxy, mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener";
import { ExtensionLoginDecryptionOptionsService } from "./extension-login-decryption-options.service";
// Mock the module providing postLogoutMessageListener$
jest.mock("../utils/post-logout-message-listener", () => {
return {
postLogoutMessageListener$: new BehaviorSubject<string>(""), // Replace with mock subject
};
});
describe("ExtensionLoginDecryptionOptionsService", () => {
let service: ExtensionLoginDecryptionOptionsService;
let messagingService: MockProxy<MessagingService>;
let router: MockProxy<Router>;
let postLogoutMessageSubject: BehaviorSubject<string>;
beforeEach(() => {
messagingService = mock<MessagingService>();
router = mock<Router>();
// Cast postLogoutMessageListener$ to BehaviorSubject for dynamic control
postLogoutMessageSubject = postLogoutMessageListener$ as BehaviorSubject<string>;
service = new ExtensionLoginDecryptionOptionsService(messagingService, router);
});
it("should instantiate the service", () => {
expect(service).not.toBeFalsy();
});
describe("logOut()", () => {
it("should send a logout message", async () => {
postLogoutMessageSubject.next("switchAccountFinish");
await service.logOut();
expect(messagingService.send).toHaveBeenCalledWith("logout");
});
it("should navigate to root on 'switchAccountFinish'", async () => {
postLogoutMessageSubject.next("switchAccountFinish");
await service.logOut();
expect(router.navigate).toHaveBeenCalledWith(["/"]);
});
it("should not navigate for 'doneLoggingOut'", async () => {
postLogoutMessageSubject.next("doneLoggingOut");
await service.logOut();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,37 @@
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import {
DefaultLoginDecryptionOptionsService,
LoginDecryptionOptionsService,
} from "@bitwarden/auth/angular";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener";
export class ExtensionLoginDecryptionOptionsService
extends DefaultLoginDecryptionOptionsService
implements LoginDecryptionOptionsService
{
constructor(
protected messagingService: MessagingService,
private router: Router,
) {
super(messagingService);
}
override async logOut(): Promise<void> {
// start listening for "switchAccountFinish" or "doneLoggingOut"
const messagePromise = firstValueFrom(postLogoutMessageListener$);
super.logOut();
// wait for messages
const command = await messagePromise;
// doneLoggingOut already has a message handler that will navigate us
if (command === "switchAccountFinish") {
await this.router.navigate(["/"]);
}
}
}

View File

@@ -1,15 +1,15 @@
import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component";
import { postLogoutMessageListener$ } from "../utils/post-logout-message-listener";
@Component({
selector: "browser-login-decryption-options",
templateUrl: "login-decryption-options.component.html",
templateUrl: "login-decryption-options-v1.component.html",
})
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 {
override async createUser(): Promise<void> {
try {
await super.createUser();

View File

@@ -21,7 +21,6 @@ import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh
import {
AnonLayoutWrapperComponent,
AnonLayoutWrapperData,
DevicesIcon,
LoginComponent,
LoginSecondaryContentComponent,
LockIcon,
@@ -37,6 +36,8 @@ import {
SetPasswordJitComponent,
UserLockIcon,
VaultIcon,
LoginDecryptionOptionsComponent,
DevicesIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -51,7 +52,7 @@ import {
import { HintComponent } from "../auth/popup/hint.component";
import { HomeComponent } from "../auth/popup/home.component";
import { LockComponent } from "../auth/popup/lock.component";
import { LoginDecryptionOptionsComponent } from "../auth/popup/login-decryption-options/login-decryption-options.component";
import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component";
import { LoginComponentV1 } from "../auth/popup/login-v1.component";
import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component";
import { RegisterComponent } from "../auth/popup/register.component";
@@ -206,12 +207,6 @@ const routes: Routes = [
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa-options" } satisfies RouteDataProperties,
},
{
path: "login-initiated",
component: LoginDecryptionOptionsComponent,
canActivate: [tdeDecryptionRequiredGuard()],
data: { state: "login-initiated" } satisfies RouteDataProperties,
},
{
path: "sso",
component: SsoComponent,
@@ -534,6 +529,23 @@ const routes: Routes = [
],
},
),
...unauthUiRefreshSwap(
LoginDecryptionOptionsComponentV1,
ExtensionAnonLayoutWrapperComponent,
{
path: "login-initiated",
canActivate: [tdeDecryptionRequiredGuard()],
data: { state: "login-initiated" } satisfies RouteDataProperties,
},
{
path: "login-initiated",
canActivate: [tdeDecryptionRequiredGuard()],
data: {
pageIcon: DevicesIcon,
},
children: [{ path: "", component: LoginDecryptionOptionsComponent }],
},
),
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,

View File

@@ -24,7 +24,7 @@ import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-ano
import { HintComponent } from "../auth/popup/hint.component";
import { HomeComponent } from "../auth/popup/home.component";
import { LockComponent } from "../auth/popup/lock.component";
import { LoginDecryptionOptionsComponent } from "../auth/popup/login-decryption-options/login-decryption-options.component";
import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component";
import { LoginComponentV1 } from "../auth/popup/login-v1.component";
import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component";
import { RegisterComponent } from "../auth/popup/register.component";
@@ -161,7 +161,7 @@ import "../platform/popup/locales";
LockComponent,
LoginViaAuthRequestComponentV1,
LoginComponentV1,
LoginDecryptionOptionsComponent,
LoginDecryptionOptionsComponentV1,
NotificationsSettingsV1Component,
AppearanceComponent,
GeneratorComponent,

View File

@@ -1,4 +1,5 @@
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, merge, of } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
@@ -22,6 +23,7 @@ import {
AnonLayoutWrapperDataService,
LoginComponentService,
LockComponentService,
LoginDecryptionOptionsService,
} from "@bitwarden/auth/angular";
import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -115,6 +117,7 @@ import { PasswordRepromptService } from "@bitwarden/vault";
import { ForegroundLockService } from "../../auth/popup/accounts/foreground-lock.service";
import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service";
import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service";
import { ExtensionLoginDecryptionOptionsService } from "../../auth/popup/login-decryption-options/extension-login-decryption-options.service";
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
import AutofillService from "../../autofill/services/autofill.service";
import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service";
@@ -591,6 +594,11 @@ const safeProviders: SafeProvider[] = [
useExisting: PopupCompactModeService,
deps: [],
}),
safeProvider({
provide: LoginDecryptionOptionsService,
useClass: ExtensionLoginDecryptionOptionsService,
deps: [MessagingServiceAbstraction, Router],
}),
];
@NgModule({