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

refactor(auth): [PM-9722] remove deprecated LoginDecryptionOptionsComponent

- Remove LoginDecryptionOptionsComponentV1
- Clean up orphaned translation messages
- Remove unused styles
- Clean up related dependencies

Closes PM-9722
This commit is contained in:
Alec Rippberger
2025-03-13 15:37:52 -05:00
committed by GitHub
parent d0f337bf98
commit e9c7cd11a3
15 changed files with 5 additions and 798 deletions

View File

@@ -3302,12 +3302,6 @@
"loginWithMasterPassword": {
"message": "Log in with master password"
},
"loggingInAs": {
"message": "Logging in as"
},
"notYou": {
"message": "Not you?"
},
"newAroundHere": {
"message": "New around here?"
},
@@ -3470,9 +3464,6 @@
"requestAdminApproval": {
"message": "Request admin approval"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
},
"ssoIdentifierRequired": {
"message": "Organization SSO identifier is required."
},

View File

@@ -1,111 +0,0 @@
<div id="login-initiated">
<app-header>
<div class="left">
<app-pop-out></app-pop-out>
</div>
<h1 class="center">
<span class="title">{{ "loginInitiated" | i18n }}</span>
</h1>
<div class="right"></div>
</app-header>
<div class="content login-page">
<div class="full-loading-spinner" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<ng-container *ngIf="!loading">
<ng-container *ngIf="data.state == State.ExistingUserUntrustedDevice">
<div class="standard-x-margin">
<p class="lead">{{ "loginInitiated" | i18n }}</p>
<h6 class="mb-20px">{{ "deviceApprovalRequired" | i18n }}</h6>
</div>
<form
id="rememberDeviceForm"
class="mb-20px standard-x-margin"
[formGroup]="rememberDeviceForm"
>
<div>
<input
type="checkbox"
id="rememberDevice"
name="rememberDevice"
formControlName="rememberDevice"
/>
<label for="rememberDevice">
{{ "rememberThisDevice" | i18n }}
</label>
<p id="rememberThisDeviceHintText">{{ "uncheckIfPublicDevice" | i18n }}</p>
</div>
</form>
<div class="box mb-20px">
<button
*ngIf="data.showApproveFromOtherDeviceBtn"
(click)="approveFromOtherDevice()"
type="button"
class="btn primary block"
>
<b>{{ "approveFromYourOtherDevice" | i18n }}</b>
</button>
<button
*ngIf="data.showReqAdminApprovalBtn"
(click)="requestAdminApproval()"
type="button"
class="btn block btn-top-margin"
>
{{ "requestAdminApproval" | i18n }}
</button>
<button
*ngIf="data.showApproveWithMasterPasswordBtn"
type="button"
class="btn block btn-top-margin"
(click)="approveWithMasterPassword()"
>
{{ "approveWithMasterPassword" | i18n }}
</button>
</div>
</ng-container>
<ng-container *ngIf="data.state == State.NewUser">
<div class="standard-x-margin">
<p class="lead">{{ "loginInitiated" | i18n }}</p>
</div>
<form
id="rememberDeviceForm"
class="mb-20px standard-x-margin"
[formGroup]="rememberDeviceForm"
>
<div>
<input
type="checkbox"
id="rememberDevice"
name="rememberDevice"
formControlName="rememberDevice"
/>
<label for="rememberDevice">
{{ "rememberThisDevice" | i18n }}
</label>
<p id="rememberThisDeviceHintText">{{ "uncheckIfPublicDevice" | i18n }}</p>
</div>
</form>
<div class="box mb-20px">
<button (click)="createUser()" type="button" class="btn primary block">
<b>{{ "continue" | i18n }}</b>
</button>
</div>
</ng-container>
<hr class="muted-hr mx-5px mb-20px" />
<div class="small mx-5px">
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ data.userEmail }}</p>
<a tabindex="0" role="button" style="cursor: pointer" (click)="logOut()">{{
"notYou" | i18n
}}</a>
</div>
</ng-container>
</div>
</div>

View File

@@ -1,39 +0,0 @@
import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
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-v1.component.html",
})
export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 {
override async createUser(): Promise<void> {
try {
await super.createUser();
await this.router.navigate(["/tabs/vault"]);
} catch (error) {
this.validationService.showError(error);
}
}
override async logOut(): Promise<void> {
// start listening for "switchAccountFinish" or "doneLoggingOut"
const messagePromise = firstValueFrom(postLogoutMessageListener$);
super.logOut();
// wait for messages
const command = await messagePromise;
// We should be routed/routing very soon but just in case, turn loading back off.
this.loading = false;
// doneLoggingOut already has a message handler that will navigate us
if (command === "switchAccountFinish") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
}
}
}

View File

@@ -20,7 +20,6 @@ import { AvatarModule, ButtonModule, FormFieldModule, ToastModule } from "@bitwa
import { AccountComponent } from "../auth/popup/account-switching/account.component";
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component";
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
@@ -91,7 +90,6 @@ import "../platform/popup/locales";
AppComponent,
ColorPasswordPipe,
ColorPasswordCountPipe,
LoginDecryptionOptionsComponentV1,
SetPasswordComponent,
SsoComponentV1,
TabsV2Component,

View File

@@ -447,37 +447,3 @@ main:not(popup-page main) {
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}
#login-initiated {
.margin-auto {
margin: auto;
}
.mb-20px {
margin-bottom: 20px;
}
.mx-5px {
margin-left: 5px !important;
margin-right: 5px !important;
}
.muted-hr {
margin-top: 1rem;
margin-bottom: 1rem;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.standard-x-margin {
margin-left: 5px;
margin-right: 5px;
}
.btn-top-margin {
margin-top: 12px;
}
#rememberThisDeviceHintText {
font-size: $font-size-small;
color: $text-muted;
}
}

View File

@@ -1,66 +0,0 @@
<div id="login-decryption-options-page">
<div id="content" class="content">
<img class="logo-image" alt="Bitwarden" />
<div class="container loading-spinner" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<ng-container *ngIf="!loading">
<h1 id="heading">{{ "loginInitiated" | i18n }}</h1>
<h6
*ngIf="data.state == State.ExistingUserUntrustedDevice"
id="subHeading"
class="standard-bottom-margin"
>
{{ "deviceApprovalRequired" | i18n }}
</h6>
<form id="rememberDeviceForm" class="standard-bottom-margin" [formGroup]="rememberDeviceForm">
<div class="checkbox">
<label for="rememberDevice">
<input
id="rememberDevice"
type="checkbox"
name="rememberDevice"
formControlName="rememberDevice"
/>
{{ "rememberThisDevice" | i18n }}
</label>
</div>
<span id="rememberThisDeviceHintText">{{ "uncheckIfPublicDevice" | i18n }}</span>
</form>
<div *ngIf="data.state == State.ExistingUserUntrustedDevice" class="buttons with-rows">
<div class="buttons-row" *ngIf="data.showApproveFromOtherDeviceBtn">
<button (click)="approveFromOtherDevice()" type="button" class="btn primary block">
{{ "approveFromYourOtherDevice" | i18n }}
</button>
</div>
<div class="buttons-row" *ngIf="data.showReqAdminApprovalBtn">
<button (click)="requestAdminApproval()" type="button" class="btn block">
{{ "requestAdminApproval" | i18n }}
</button>
</div>
<div class="buttons-row" *ngIf="data.showApproveWithMasterPasswordBtn">
<button (click)="approveWithMasterPassword()" type="button" class="btn block">
{{ "approveWithMasterPassword" | i18n }}
</button>
</div>
</div>
<div *ngIf="data.state == State.NewUser" class="buttons with-rows">
<div class="buttons-row">
<button (click)="createUser()" type="button" class="btn block">
{{ "continue" | i18n }}
</button>
</div>
</div>
<div style="text-align: center">
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ data.userEmail }}</p>
<a [routerLink]="[]" (click)="logOut()">{{ "notYou" | i18n }}</a>
</div>
</ng-container>
</div>
</div>

View File

@@ -1,19 +0,0 @@
import { Component } from "@angular/core";
import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component";
@Component({
selector: "desktop-login-decryption-options",
templateUrl: "login-decryption-options-v1.component.html",
})
export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 {
override async createUser(): Promise<void> {
try {
await super.createUser();
this.messagingService.send("redrawMenu");
await this.router.navigate(["/vault"]);
} catch (error) {
this.validationService.showError(error);
}
}
}

View File

@@ -5,11 +5,9 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components
import { SharedModule } from "../../app/shared/shared.module";
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
@NgModule({
imports: [SharedModule, RouterModule],
declarations: [EnvironmentSelectorComponent, LoginDecryptionOptionsComponentV1],
declarations: [EnvironmentSelectorComponent],
exports: [],
})
export class LoginModule {}

View File

@@ -2772,15 +2772,9 @@
"loginWithMasterPassword": {
"message": "Log in with master password"
},
"loggingInAs": {
"message": "Logging in as"
},
"rememberEmail": {
"message": "Remember email"
},
"notYou": {
"message": "Not you?"
},
"newAroundHere": {
"message": "New around here?"
},
@@ -3020,9 +3014,6 @@
"requestAdminApproval": {
"message": "Request admin approval"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
},
"region": {
"message": "Region"
},

View File

@@ -1,11 +1,9 @@
@import "variables.scss";
#login-page,
#lock-page,
#sso-page,
#set-password-page,
#remove-password-page,
#login-decryption-options-page {
#remove-password-page {
display: flex;
justify-content: center;
align-items: center;
@@ -48,13 +46,11 @@
}
#accessibility-cookie-page,
#login-page,
#register-page,
#hint-page,
#two-factor-page,
#lock-page,
#update-temp-password-page,
#login-decryption-options-page {
#update-temp-password-page {
.content {
width: 325px;
transition: width 0.25s linear;
@@ -189,37 +185,6 @@
}
}
#login-page,
#login-decryption-options-page {
flex-direction: column;
justify-content: unset;
padding-top: 20px;
.login-header {
align-self: flex-start;
padding: 1em;
font-size: 1.2em;
.environment-urls-settings-icon {
@include themify($themes) {
color: themed("mutedColor");
}
span {
visibility: hidden;
}
&:hover,
&:focus {
text-decoration: none;
@include themify($themes) {
color: themed("primaryColor");
}
}
}
}
}
#login-approval-page {
.section-title {
padding: 20px;
@@ -239,14 +204,3 @@
}
}
}
#login-decryption-options-page {
.standard-bottom-margin {
margin-bottom: 20px;
}
#rememberThisDeviceHintText {
font-size: $font-size-small;
color: $text-muted;
}
}

View File

@@ -1,105 +0,0 @@
<div class="tw-container tw-mx-auto">
<div
class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8"
>
<div class="tw-mb-6">
<img class="logo logo-themed" alt="Bitwarden" />
</div>
<ng-container *ngIf="loading">
<p class="text-center">
<i
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</p>
</ng-container>
<div
*ngIf="!loading"
class="tw-w-full tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
>
<ng-container *ngIf="data.state == State.ExistingUserUntrustedDevice">
<h2 bitTypography="h2" class="tw-mb-6">{{ "loginInitiated" | i18n }}</h2>
<p bitTypography="body1" class="tw-mb-6">
{{ "deviceApprovalRequired" | i18n }}
</p>
<form [formGroup]="rememberDeviceForm">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="rememberDevice" />
<bit-label>{{ "rememberThisDevice" | i18n }} </bit-label>
<bit-hint bitTypography="body2">{{ "uncheckIfPublicDevice" | i18n }}</bit-hint>
</bit-form-control>
</form>
<div class="tw-mb-6 tw-flex tw-flex-col tw-space-y-3">
<button
*ngIf="data.showApproveFromOtherDeviceBtn"
(click)="approveFromOtherDevice()"
bitButton
type="button"
buttonType="primary"
block
>
{{ "approveFromYourOtherDevice" | i18n }}
</button>
<button
*ngIf="data.showReqAdminApprovalBtn"
(click)="requestAdminApproval()"
bitButton
type="button"
buttonType="secondary"
>
{{ "requestAdminApproval" | i18n }}
</button>
<button
*ngIf="data.showApproveWithMasterPasswordBtn"
(click)="approveWithMasterPassword()"
bitButton
type="button"
buttonType="secondary"
block
>
{{ "approveWithMasterPassword" | i18n }}
</button>
</div>
</ng-container>
<ng-container *ngIf="data.state == State.NewUser">
<h2 bitTypography="h2" class="tw-mb-6">{{ "loggedInExclamation" | i18n }}</h2>
<form [formGroup]="rememberDeviceForm">
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="rememberDevice" />
<bit-label>{{ "rememberThisDevice" | i18n }} </bit-label>
<bit-hint bitTypography="body2">{{ "uncheckIfPublicDevice" | i18n }}</bit-hint>
</bit-form-control>
</form>
<button
bitButton
type="button"
buttonType="primary"
block
class="tw-mb-6"
[bitAction]="createUserAction"
>
{{ "continue" | i18n }}
</button>
</ng-container>
<hr class="tw-mb-6 tw-mt-0" />
<div class="tw-m-0 tw-text-sm">
<p class="tw-mb-1">{{ "loggingInAs" | i18n }} {{ data.userEmail }}</p>
<a [routerLink]="[]" (click)="logOut()">{{ "notYou" | i18n }}</a>
</div>
</div>
</div>
</div>

View File

@@ -1,34 +0,0 @@
import { Component, inject } from "@angular/core";
import { BaseLoginDecryptionOptionsComponentV1 } from "@bitwarden/angular/auth/components/base-login-decryption-options-v1.component";
import { RouterService } from "../../../core";
import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service";
@Component({
selector: "web-login-decryption-options",
templateUrl: "login-decryption-options-v1.component.html",
})
export class LoginDecryptionOptionsComponentV1 extends BaseLoginDecryptionOptionsComponentV1 {
protected routerService = inject(RouterService);
protected acceptOrganizationInviteService = inject(AcceptOrganizationInviteService);
override async createUser(): Promise<void> {
try {
await super.createUser();
// Invites from TDE orgs go through here, but the invite is
// accepted while being enrolled in admin recovery. So we need to clear
// the redirect and stored org invite.
await this.routerService.getAndClearLoginRedirectUrl();
await this.acceptOrganizationInviteService.clearOrganizationInvitation();
await this.router.navigate(["/vault"]);
} catch (error) {
this.validationService.showError(error);
}
}
createUserAction = async (): Promise<void> => {
return this.createUser();
};
}

View File

@@ -4,12 +4,11 @@ import { CheckboxModule } from "@bitwarden/components";
import { SharedModule } from "../../../app/shared";
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component";
@NgModule({
imports: [SharedModule, CheckboxModule],
declarations: [LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent],
exports: [LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent],
declarations: [LoginViaWebAuthnComponent],
exports: [LoginViaWebAuthnComponent],
})
export class LoginModule {}

View File

@@ -7329,12 +7329,6 @@
"numberOfUsers": {
"message": "Number of users"
},
"loggingInAs": {
"message": "Logging in as"
},
"notYou": {
"message": "Not you?"
},
"pickAnAvatarColor": {
"message": "Pick an avatar color"
},
@@ -8458,9 +8452,6 @@
"requestAdminApproval": {
"message": "Request admin approval"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
},
"trustedDeviceEncryption": {
"message": "Trusted device encryption"
},

View File

@@ -1,307 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import {
firstValueFrom,
switchMap,
Subject,
catchError,
from,
of,
finalize,
takeUntil,
defer,
throwError,
map,
Observable,
take,
} from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import {
LoginEmailServiceAbstraction,
UserDecryptionOptions,
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
enum State {
NewUser,
ExistingUserUntrustedDevice,
}
type NewUserData = {
readonly state: State.NewUser;
readonly organizationId: string;
readonly userEmail: string;
};
type ExistingUserUntrustedDeviceData = {
readonly state: State.ExistingUserUntrustedDevice;
readonly showApproveFromOtherDeviceBtn: boolean;
readonly showReqAdminApprovalBtn: boolean;
readonly showApproveWithMasterPasswordBtn: boolean;
readonly userEmail: string;
};
type Data = NewUserData | ExistingUserUntrustedDeviceData;
@Directive()
export class BaseLoginDecryptionOptionsComponentV1 implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected State = State;
protected data?: Data;
protected loading = true;
private email$: Observable<string>;
activeAccountId: UserId;
// Remember device means for the user to trust the device
rememberDeviceForm = this.formBuilder.group({
rememberDevice: [true],
});
get rememberDevice(): FormControl<boolean> {
return this.rememberDeviceForm?.controls.rememberDevice;
}
constructor(
protected formBuilder: FormBuilder,
protected devicesService: DevicesServiceAbstraction,
protected stateService: StateService,
protected router: Router,
protected activatedRoute: ActivatedRoute,
protected messagingService: MessagingService,
protected tokenService: TokenService,
protected loginEmailService: LoginEmailServiceAbstraction,
protected organizationApiService: OrganizationApiServiceAbstraction,
protected keyService: KeyService,
protected organizationUserApiService: OrganizationUserApiService,
protected apiService: ApiService,
protected i18nService: I18nService,
protected validationService: ValidationService,
protected deviceTrustService: DeviceTrustServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {}
async ngOnInit() {
this.loading = true;
this.activeAccountId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.email$ = this.accountService.activeAccount$.pipe(
map((a) => a?.email),
catchError((err: unknown) => {
this.validationService.showError(err);
return of(undefined);
}),
takeUntil(this.destroy$),
);
this.setupRememberDeviceValueChanges();
// Persist user choice from state if it exists
await this.setRememberDeviceDefaultValue();
try {
const userDecryptionOptions = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptions$,
);
// see sso-login.strategy - to determine if a user is new or not it just checks if there is a key on the token response..
// can we check if they have a user key or master key in crypto service? Would that be sufficient?
if (
!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
!userDecryptionOptions?.hasMasterPassword
) {
// We are dealing with a new account if:
// - User does not have admin approval (i.e. has not enrolled into admin reset)
// - AND does not have a master password
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.loadNewUserData();
} else {
this.loadUntrustedDeviceData(userDecryptionOptions);
}
// Note: this is probably not a comprehensive write up of all scenarios:
// If the TDE feature flag is enabled and TDE is configured for the org that the user is a member of,
// then new and existing users can be redirected here after completing the SSO flow (and 2FA if enabled).
// First we must determine user type (new or existing):
// New User
// - present user with option to remember the device or not (trust the device)
// - present a continue button to proceed to the vault
// - loadNewUserData() --> will need to load enrollment status and user email address.
// Existing User
// - Determine if user is an admin with access to account recovery in admin console
// - Determine if user has a MP or not, if not, they must be redirected to set one (see PM-1035)
// - Determine if device is trusted or not via device crypto service (method not yet written)
// - If not trusted, present user with login decryption options (approve from other device, approve with master password, request admin approval)
// - loadUntrustedDeviceData()
} catch (err) {
this.validationService.showError(err);
}
}
private async setRememberDeviceDefaultValue() {
const rememberDeviceFromState = await this.deviceTrustService.getShouldTrustDevice(
this.activeAccountId,
);
const rememberDevice = rememberDeviceFromState ?? true;
this.rememberDevice.setValue(rememberDevice);
}
private setupRememberDeviceValueChanges() {
this.rememberDevice.valueChanges
.pipe(
switchMap((value) =>
defer(() => this.deviceTrustService.setShouldTrustDevice(this.activeAccountId, value)),
),
takeUntil(this.destroy$),
)
.subscribe();
}
async loadNewUserData() {
const autoEnrollStatus$ = defer(() =>
this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeAccountId),
).pipe(
switchMap((organizationIdentifier) => {
if (organizationIdentifier == undefined) {
return throwError(() => new Error(this.i18nService.t("ssoIdentifierRequired")));
}
return from(this.organizationApiService.getAutoEnrollStatus(organizationIdentifier));
}),
catchError((err: unknown) => {
this.validationService.showError(err);
return of(undefined);
}),
);
const autoEnrollStatus = await firstValueFrom(autoEnrollStatus$);
const email = await firstValueFrom(this.email$);
this.data = { state: State.NewUser, organizationId: autoEnrollStatus.id, userEmail: email };
this.loading = false;
}
loadUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) {
this.loading = true;
this.email$
.pipe(
take(1),
finalize(() => {
this.loading = false;
}),
)
.subscribe((email) => {
const showApproveFromOtherDeviceBtn =
userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
const showReqAdminApprovalBtn =
!!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
const showApproveWithMasterPasswordBtn = userDecryptionOptions?.hasMasterPassword || false;
const userEmail = email;
this.data = {
state: State.ExistingUserUntrustedDevice,
showApproveFromOtherDeviceBtn,
showReqAdminApprovalBtn,
showApproveWithMasterPasswordBtn,
userEmail,
};
});
}
async approveFromOtherDevice() {
if (this.data.state !== State.ExistingUserUntrustedDevice) {
return;
}
this.loginEmailService.setLoginEmail(this.data.userEmail);
await this.router.navigate(["/login-with-device"]);
}
async requestAdminApproval() {
this.loginEmailService.setLoginEmail(this.data.userEmail);
await this.router.navigate(["/admin-approval-requested"]);
}
async approveWithMasterPassword() {
await this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } });
}
async createUser() {
if (this.data.state !== State.NewUser) {
return;
}
// this.loading to support clients without async-actions-support
this.loading = true;
// errors must be caught in child components to prevent navigation
try {
const { publicKey, privateKey } = await this.keyService.initAccount();
const keysRequest = new KeysRequest(publicKey, privateKey.encryptedString);
await this.apiService.postAccountKeys(keysRequest);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("accountSuccessfullyCreated"),
});
await this.passwordResetEnrollmentService.enroll(this.data.organizationId);
if (this.rememberDeviceForm.value.rememberDevice) {
await this.deviceTrustService.trustDevice(this.activeAccountId);
}
} finally {
this.loading = false;
}
}
logOut() {
this.loading = true; // to avoid an awkward delay in browser extension
this.messagingService.send("logout");
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}