mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +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": {
|
"loginWithMasterPassword": {
|
||||||
"message": "Log in with master password"
|
"message": "Log in with master password"
|
||||||
},
|
},
|
||||||
"loggingInAs": {
|
|
||||||
"message": "Logging in as"
|
|
||||||
},
|
|
||||||
"notYou": {
|
|
||||||
"message": "Not you?"
|
|
||||||
},
|
|
||||||
"newAroundHere": {
|
"newAroundHere": {
|
||||||
"message": "New around here?"
|
"message": "New around here?"
|
||||||
},
|
},
|
||||||
@@ -3470,9 +3464,6 @@
|
|||||||
"requestAdminApproval": {
|
"requestAdminApproval": {
|
||||||
"message": "Request admin approval"
|
"message": "Request admin approval"
|
||||||
},
|
},
|
||||||
"approveWithMasterPassword": {
|
|
||||||
"message": "Approve with master password"
|
|
||||||
},
|
|
||||||
"ssoIdentifierRequired": {
|
"ssoIdentifierRequired": {
|
||||||
"message": "Organization SSO identifier is required."
|
"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 { AccountComponent } from "../auth/popup/account-switching/account.component";
|
||||||
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-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 { 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 { RemovePasswordComponent } from "../auth/popup/remove-password.component";
|
||||||
import { SetPasswordComponent } from "../auth/popup/set-password.component";
|
import { SetPasswordComponent } from "../auth/popup/set-password.component";
|
||||||
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
|
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
|
||||||
@@ -91,7 +90,6 @@ import "../platform/popup/locales";
|
|||||||
AppComponent,
|
AppComponent,
|
||||||
ColorPasswordPipe,
|
ColorPasswordPipe,
|
||||||
ColorPasswordCountPipe,
|
ColorPasswordCountPipe,
|
||||||
LoginDecryptionOptionsComponentV1,
|
|
||||||
SetPasswordComponent,
|
SetPasswordComponent,
|
||||||
SsoComponentV1,
|
SsoComponentV1,
|
||||||
TabsV2Component,
|
TabsV2Component,
|
||||||
|
|||||||
@@ -447,37 +447,3 @@ main:not(popup-page main) {
|
|||||||
.cdk-virtual-scroll-content-wrapper {
|
.cdk-virtual-scroll-content-wrapper {
|
||||||
width: 100%;
|
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 { SharedModule } from "../../app/shared/shared.module";
|
||||||
|
|
||||||
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, RouterModule],
|
imports: [SharedModule, RouterModule],
|
||||||
declarations: [EnvironmentSelectorComponent, LoginDecryptionOptionsComponentV1],
|
declarations: [EnvironmentSelectorComponent],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
export class LoginModule {}
|
export class LoginModule {}
|
||||||
|
|||||||
@@ -2772,15 +2772,9 @@
|
|||||||
"loginWithMasterPassword": {
|
"loginWithMasterPassword": {
|
||||||
"message": "Log in with master password"
|
"message": "Log in with master password"
|
||||||
},
|
},
|
||||||
"loggingInAs": {
|
|
||||||
"message": "Logging in as"
|
|
||||||
},
|
|
||||||
"rememberEmail": {
|
"rememberEmail": {
|
||||||
"message": "Remember email"
|
"message": "Remember email"
|
||||||
},
|
},
|
||||||
"notYou": {
|
|
||||||
"message": "Not you?"
|
|
||||||
},
|
|
||||||
"newAroundHere": {
|
"newAroundHere": {
|
||||||
"message": "New around here?"
|
"message": "New around here?"
|
||||||
},
|
},
|
||||||
@@ -3020,9 +3014,6 @@
|
|||||||
"requestAdminApproval": {
|
"requestAdminApproval": {
|
||||||
"message": "Request admin approval"
|
"message": "Request admin approval"
|
||||||
},
|
},
|
||||||
"approveWithMasterPassword": {
|
|
||||||
"message": "Approve with master password"
|
|
||||||
},
|
|
||||||
"region": {
|
"region": {
|
||||||
"message": "Region"
|
"message": "Region"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
#login-page,
|
|
||||||
#lock-page,
|
#lock-page,
|
||||||
#sso-page,
|
#sso-page,
|
||||||
#set-password-page,
|
#set-password-page,
|
||||||
#remove-password-page,
|
#remove-password-page {
|
||||||
#login-decryption-options-page {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -48,13 +46,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#accessibility-cookie-page,
|
#accessibility-cookie-page,
|
||||||
#login-page,
|
|
||||||
#register-page,
|
#register-page,
|
||||||
#hint-page,
|
#hint-page,
|
||||||
#two-factor-page,
|
#two-factor-page,
|
||||||
#lock-page,
|
#lock-page,
|
||||||
#update-temp-password-page,
|
#update-temp-password-page {
|
||||||
#login-decryption-options-page {
|
|
||||||
.content {
|
.content {
|
||||||
width: 325px;
|
width: 325px;
|
||||||
transition: width 0.25s linear;
|
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 {
|
#login-approval-page {
|
||||||
.section-title {
|
.section-title {
|
||||||
padding: 20px;
|
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 { 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";
|
import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, CheckboxModule],
|
imports: [SharedModule, CheckboxModule],
|
||||||
declarations: [LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent],
|
declarations: [LoginViaWebAuthnComponent],
|
||||||
exports: [LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent],
|
exports: [LoginViaWebAuthnComponent],
|
||||||
})
|
})
|
||||||
export class LoginModule {}
|
export class LoginModule {}
|
||||||
|
|||||||
@@ -7329,12 +7329,6 @@
|
|||||||
"numberOfUsers": {
|
"numberOfUsers": {
|
||||||
"message": "Number of users"
|
"message": "Number of users"
|
||||||
},
|
},
|
||||||
"loggingInAs": {
|
|
||||||
"message": "Logging in as"
|
|
||||||
},
|
|
||||||
"notYou": {
|
|
||||||
"message": "Not you?"
|
|
||||||
},
|
|
||||||
"pickAnAvatarColor": {
|
"pickAnAvatarColor": {
|
||||||
"message": "Pick an avatar color"
|
"message": "Pick an avatar color"
|
||||||
},
|
},
|
||||||
@@ -8458,9 +8452,6 @@
|
|||||||
"requestAdminApproval": {
|
"requestAdminApproval": {
|
||||||
"message": "Request admin approval"
|
"message": "Request admin approval"
|
||||||
},
|
},
|
||||||
"approveWithMasterPassword": {
|
|
||||||
"message": "Approve with master password"
|
|
||||||
},
|
|
||||||
"trustedDeviceEncryption": {
|
"trustedDeviceEncryption": {
|
||||||
"message": "Trusted device encryption"
|
"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