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:
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
@@ -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(["/"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user