1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

chore(feature flags): [PM-19034] Remove feature flags and old components for Set/Change Password

* Removed flag and components.

* More cleanup

* Removed ChangePasswordComponent.

* Removed old EmergencyAccessTakeover

* Removed service initialization.

* Fixed test failures.

* Fixed tests.

* Test changes.

* Updated comments

* Fixed tests.

* Fixed tests.

* Fixed merge conflict.

* Removed style and routing references.

* Better comments.

* Removed ResetPasswordComponent
This commit is contained in:
Todd Martin
2025-07-24 12:46:18 -04:00
committed by GitHub
parent df8e0ed094
commit b3db1b79ce
65 changed files with 247 additions and 4487 deletions

View File

@@ -1,160 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" routerLink="/login">{{ "cancel" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ "setMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1">
<div class="full-loading-spinner" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="!syncLoading">
<div class="box">
<p
class="tw-px-4"
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p class="tw-px-4">{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
aria-describedby="masterPasswordHelp"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
<div id="masterPasswordHelp" class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input
id="hint"
type="text"
name="Hint"
aria-describedby="hintHelp"
[(ngModel)]="hint"
/>
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</div>
</main>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent {}

View File

@@ -1,142 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" (click)="logOut()">{{ "logOut" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ "updateMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ masterPasswordWarningText }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box" *ngIf="requireCurrentPassword">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="currentMasterPassword">
{{ "currentMasterPass" | i18n }}
</label>
<input
id="currentMasterPassword"
type="password"
name="CurrentMasterPassword"
class="monospaced"
[(ngModel)]="verification.secret"
required
appInputVerbatim
/>
</div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "newMasterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" aria-describedby="hintHelp" [(ngModel)]="hint" />
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</main>
</form>

View File

@@ -1,30 +0,0 @@
import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
onSuccessfulChangePassword: () => Promise<void> = this.doOnSuccessfulChangePassword.bind(this);
private async doOnSuccessfulChangePassword() {
// start listening for "switchAccountFinish" or "doneLoggingOut"
const messagePromise = firstValueFrom(postLogoutMessageListener$);
this.messagingService.send("logout");
// wait for messages
const command = await messagePromise;
// 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

@@ -32,7 +32,6 @@ import {
RegistrationStartSecondaryComponent,
RegistrationStartSecondaryComponentData,
RegistrationUserAddIcon,
SetPasswordJitComponent,
SsoComponent,
TwoFactorTimeoutIcon,
TwoFactorAuthComponent,
@@ -43,15 +42,13 @@ import {
VaultIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import { fido2AuthGuard } from "../auth/popup/guards/fido2-auth.guard";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { ExtensionDeviceManagementComponent } from "../auth/popup/settings/extension-device-management.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { Fido2Component } from "../autofill/popup/fido2/fido2.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-domains.component";
@@ -180,11 +177,6 @@ const routes: Routes = [
elevation: 1,
} satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
},
{
path: "set-password",
component: SetPasswordComponent,
data: { elevation: 1 } satisfies RouteDataProperties,
},
{
path: "remove-password",
component: RemovePasswordComponent,
@@ -337,20 +329,6 @@ const routes: Routes = [
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
`/change-password`,
false,
),
authGuard,
],
data: { elevation: 1 } satisfies RouteDataProperties,
},
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
@@ -398,7 +376,7 @@ const routes: Routes = [
},
{
path: "set-initial-password",
canActivate: [canAccessFeature(FeatureFlag.PM16117_SetInitialPasswordRefactor), authGuard],
canActivate: [authGuard],
component: SetInitialPasswordComponent,
data: {
elevation: 1,
@@ -586,29 +564,7 @@ const routes: Routes = [
component: ChangePasswordComponent,
},
],
canActivate: [
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
authGuard,
],
},
],
},
{
path: "",
component: AnonLayoutWrapperComponent,
children: [
{
path: "set-password-jit",
component: SetPasswordJitComponent,
data: {
pageTitle: {
key: "joinOrganization",
},
pageSubtitle: {
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
},
elevation: 1,
} satisfies RouteDataProperties & AnonLayoutWrapperData,
canActivate: [authGuard],
},
],
},

View File

@@ -26,10 +26,8 @@ import {
import { AccountComponent } from "../auth/popup/account-switching/account.component";
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
@@ -96,9 +94,7 @@ import "../platform/popup/locales";
AppComponent,
ColorPasswordPipe,
ColorPasswordCountPipe,
SetPasswordComponent,
TabsV2Component,
UpdateTempPasswordComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
RemovePasswordComponent,

View File

@@ -15,8 +15,6 @@ import {
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
LoginComponent,
LoginSecondaryContentComponent,
@@ -28,7 +26,6 @@ import {
RegistrationStartSecondaryComponent,
RegistrationStartSecondaryComponentData,
RegistrationUserAddIcon,
SetPasswordJitComponent,
UserLockIcon,
VaultIcon,
LoginDecryptionOptionsComponent,
@@ -40,13 +37,10 @@ import {
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { SetPasswordComponent } from "../auth/set-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
import { VaultV2Component } from "../vault/app/vault/vault-v2.component";
@@ -105,25 +99,11 @@ const routes: Routes = [
component: VaultV2Component,
canActivate: [authGuard],
},
{ path: "set-password", component: SetPasswordComponent },
{
path: "send",
component: SendComponent,
canActivate: [authGuard],
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
`/change-password`,
false,
),
authGuard,
],
},
{
path: "remove-password",
component: RemovePasswordComponent,
@@ -308,26 +288,6 @@ const routes: Routes = [
},
],
},
{
path: "set-password-jit",
component: SetPasswordJitComponent,
data: {
pageTitle: {
key: "joinOrganization",
},
pageSubtitle: {
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
},
} satisfies AnonLayoutWrapperData,
},
{
path: "set-initial-password",
canActivate: [canAccessFeature(FeatureFlag.PM16117_SetInitialPasswordRefactor), authGuard],
component: SetInitialPasswordComponent,
data: {
maxWidth: "lg",
} satisfies AnonLayoutWrapperData,
},
{
path: "2fa",
canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
@@ -346,10 +306,7 @@ const routes: Routes = [
{
path: "change-password",
component: ChangePasswordComponent,
canActivate: [
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
authGuard,
],
canActivate: [authGuard],
},
],
},

View File

@@ -13,8 +13,6 @@ import { AssignCollectionsComponent } from "@bitwarden/vault";
import { DeleteAccountComponent } from "../auth/delete-account.component";
import { LoginModule } from "../auth/login/login.module";
import { SetPasswordComponent } from "../auth/set-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { SshAgentService } from "../autofill/services/ssh-agent.service";
import { PremiumComponent } from "../billing/app/accounts/premium.component";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
@@ -57,9 +55,7 @@ import { SharedModule } from "./shared/shared.module";
PremiumComponent,
RemovePasswordComponent,
SearchComponent,
SetPasswordComponent,
SettingsComponent,
UpdateTempPasswordComponent,
VaultTimeoutInputComponent,
],
providers: [SshAgentService],

View File

@@ -1,21 +0,0 @@
import { inject } from "@angular/core";
import {
DefaultSetPasswordJitService,
SetPasswordCredentials,
SetPasswordJitService,
} from "@bitwarden/auth/angular";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
export class DesktopSetPasswordJitService
extends DefaultSetPasswordJitService
implements SetPasswordJitService
{
messagingService = inject(MessagingService);
override async setPassword(credentials: SetPasswordCredentials) {
await super.setPassword(credentials);
this.messagingService.send("redrawMenu");
}
}

View File

@@ -25,7 +25,6 @@ import {
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import {
LoginComponentService,
SetPasswordJitService,
SsoComponentService,
DefaultSsoComponentService,
TwoFactorAuthDuoComponentService,
@@ -139,7 +138,6 @@ import { NativeMessagingService } from "../../services/native-messaging.service"
import { SearchBarService } from "../layout/search/search-bar.service";
import { DesktopFileDownloadService } from "./desktop-file-download.service";
import { DesktopSetPasswordJitService } from "./desktop-set-password-jit.service";
import { InitService } from "./init.service";
import { NativeMessagingManifestService } from "./native-messaging-manifest.service";
import { DesktopSetInitialPasswordService } from "./set-initial-password/desktop-set-initial-password.service";
@@ -379,21 +377,6 @@ const safeProviders: SafeProvider[] = [
provide: CLIENT_TYPE,
useValue: ClientType.Desktop,
}),
safeProvider({
provide: SetPasswordJitService,
useClass: DesktopSetPasswordJitService,
deps: [
EncryptService,
I18nServiceAbstraction,
KdfConfigService,
KeyService,
MasterPasswordApiService,
InternalMasterPasswordServiceAbstraction,
OrganizationApiServiceAbstraction,
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
],
}),
safeProvider({
provide: SetInitialPasswordService,
useClass: DesktopSetInitialPasswordService,

View File

@@ -1,169 +0,0 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<p
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p>{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
aria-describedby="masterPasswordHelp"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
<div id="masterPasswordHelp" class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input
id="hint"
type="text"
name="Hint"
aria-describedby="hintHelp"
[(ngModel)]="hint"
/>
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button type="button" class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@@ -1,111 +0,0 @@
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
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 { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
const BroadcasterSubscriptionId = "SetPasswordComponent";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
constructor(
protected accountService: AccountService,
protected dialogService: DialogService,
protected encryptService: EncryptService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordApiService: MasterPasswordApiService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected platformUtilsService: PlatformUtilsService,
protected policyApiService: PolicyApiServiceAbstraction,
protected policyService: PolicyService,
protected route: ActivatedRoute,
protected router: Router,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected syncService: SyncService,
protected toastService: ToastService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
) {
super(
accountService,
dialogService,
encryptService,
i18nService,
kdfConfigService,
keyService,
masterPasswordApiService,
masterPasswordService,
messagingService,
organizationApiService,
organizationUserApiService,
platformUtilsService,
policyApiService,
policyService,
route,
router,
ssoLoginService,
syncService,
toastService,
userDecryptionOptionsService,
);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
protected async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString],
): Promise<void> {
await super.onSetPasswordSuccess(masterKey, userKey, keyPair);
this.messagingService.send("redrawMenu");
}
}

View File

@@ -1,136 +0,0 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ masterPasswordWarningText }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box" *ngIf="requireCurrentPassword">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="currentMasterPassword">
{{ "currentMasterPass" | i18n }}
</label>
<input
id="currentMasterPassword"
type="password"
name="currentMasterPassword"
class="monospaced"
[(ngModel)]="verification.secret"
required
appInputVerbatim
/>
</div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "newMasterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" aria-describedby="hintHelp" [(ngModel)]="hint" />
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</button>
</div>
</div>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}

View File

@@ -1,7 +1,5 @@
@import "variables.scss";
#lock-page,
#set-password-page,
#remove-password-page {
display: flex;
justify-content: center;
@@ -23,9 +21,6 @@
}
}
#register-page,
#hint-page,
#update-temp-password-page,
#remove-password-page {
padding-top: 20px;
@@ -42,68 +37,6 @@
}
}
#register-page,
#hint-page,
#lock-page,
#update-temp-password-page {
.content {
width: 325px;
transition: width 0.25s linear;
p {
text-align: center;
}
p.lead,
h1 {
font-size: $font-size-large;
text-align: center;
margin-bottom: 20px;
font-weight: normal;
}
.box {
margin-bottom: 20px;
}
.buttons {
&:not(.with-rows),
.buttons-row {
display: flex;
margin-bottom: 10px;
}
&:not(.with-rows),
.buttons-row:last-child {
margin-bottom: 20px;
}
button {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
.sub-options {
text-align: center;
margin-bottom: 20px;
a {
display: block;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
#set-password-page,
#remove-password-page {
.content {
width: 500px;
@@ -155,35 +88,8 @@
}
}
#register-page,
#update-temp-password-page {
.content {
width: 400px;
}
}
#remove-password-page {
.content > p {
margin-bottom: 20px;
}
}
#login-approval-page {
.section-title {
padding: 20px;
}
.content {
padding: 16px;
.section {
margin-bottom: 30px;
code {
@include themify($themes) {
color: themed("codeColor");
}
}
h4.label {
font-weight: bold;
}
}
}
}

View File

@@ -1,67 +0,0 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog [title]="'recoverAccount' | i18n" [subtitle]="data.name">
<ng-container bitDialogContent>
<bit-callout type="warning"
>{{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }}
</bit-callout>
<auth-password-callout
[policy]="enforcedPolicyOptions"
message="resetPasswordMasterPasswordPolicyInEffect"
*ngIf="enforcedPolicyOptions"
>
</auth-password-callout>
<bit-form-field>
<bit-label>
{{ "newPassword" | i18n }}
</bit-label>
<input
id="newPassword"
bitInput
[type]="showPassword ? 'text' : 'password'"
name="NewPassword"
formControlName="newPassword"
required
appInputVerbatim
autocomplete="new-password"
/>
<button
type="button"
bitIconButton="bwi-generate"
bitSuffix
[appA11yTitle]="'generatePassword' | i18n"
(click)="generatePassword()"
></button>
<button
type="button"
bitSuffix
[bitIconButton]="showPassword ? 'bwi-eye-slash' : 'bwi-eye'"
buttonType="secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
></button>
<button
type="button"
bitSuffix
bitIconButton="bwi-clone"
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy()"
></button>
</bit-form-field>
<tools-password-strength
[password]="formGroup.value.newPassword"
[email]="data.email"
[showText]="true"
(passwordStrengthScore)="getStrengthScore($event)"
>
</tools-password-strength>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton buttonType="primary" bitFormButton type="submit">
{{ "save" | i18n }}
</button>
<button bitButton buttonType="secondary" bitDialogClose type="button">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -1,223 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, switchMap, takeUntil } from "rxjs";
import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
DIALOG_DATA,
DialogConfig,
DialogRef,
DialogService,
ToastService,
} from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { OrganizationUserResetPasswordService } from "../services/organization-user-reset-password/organization-user-reset-password.service";
/**
* Encapsulates a few key data inputs needed to initiate an account recovery
* process for the organization user in question.
*/
export type ResetPasswordDialogData = {
/**
* The organization user's full name
*/
name: string;
/**
* The organization user's email address
*/
email: string;
/**
* The `organizationUserId` for the user
*/
id: string;
/**
* The organization's `organizationId`
*/
organizationId: OrganizationId;
};
// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum ResetPasswordDialogResult {
Ok = "ok",
}
/**
* Used in a dialog for initiating the account recovery process against a
* given organization user. An admin will access this form when they want to
* reset a user's password and log them out of sessions.
*
* @deprecated Use the `AccountRecoveryDialogComponent` instead.
*/
@Component({
selector: "app-reset-password",
templateUrl: "reset-password.component.html",
standalone: false,
})
export class ResetPasswordComponent implements OnInit, OnDestroy {
formGroup = this.formBuilder.group({
newPassword: ["", Validators.required],
});
@ViewChild(PasswordStrengthV2Component) passwordStrengthComponent: PasswordStrengthV2Component;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword = false;
passwordStrengthScore: number;
private destroy$ = new Subject<void>();
constructor(
@Inject(DIALOG_DATA) protected data: ResetPasswordDialogData,
private resetPasswordService: OrganizationUserResetPasswordService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private passwordGenerationService: PasswordGenerationServiceAbstraction,
private policyService: PolicyService,
private logService: LogService,
private dialogService: DialogService,
private toastService: ToastService,
private formBuilder: FormBuilder,
private dialogRef: DialogRef<ResetPasswordDialogResult>,
private accountService: AccountService,
) {}
async ngOnInit() {
this.accountService.activeAccount$
.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
takeUntil(this.destroy$),
)
.subscribe(
(enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions = enforcedPasswordPolicyOptions),
);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get loggedOutWarningName() {
return this.data.name != null ? this.data.name : this.i18nService.t("thisUser");
}
async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
this.formGroup.patchValue({
newPassword: await this.passwordGenerationService.generatePassword(options),
});
this.passwordStrengthComponent.updatePasswordStrength(this.formGroup.value.newPassword);
}
togglePassword() {
this.showPassword = !this.showPassword;
document.getElementById("newPassword").focus();
}
copy() {
const value = this.formGroup.value.newPassword;
if (value == null) {
return;
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
});
}
submit = async () => {
// Validation
if (this.formGroup.value.newPassword == null || this.formGroup.value.newPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return false;
}
if (this.formGroup.value.newPassword.length < Utils.minimumPasswordLength) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordMinlength", Utils.minimumPasswordLength),
});
return false;
}
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
this.passwordStrengthScore,
this.formGroup.value.newPassword,
this.enforcedPolicyOptions,
)
) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"),
});
return;
}
if (this.passwordStrengthScore < 3) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return false;
}
}
try {
await this.resetPasswordService.resetMasterPassword(
this.formGroup.value.newPassword,
this.data.email,
this.data.id,
this.data.organizationId,
);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("resetPasswordSuccess"),
});
} catch (e) {
this.logService.error(e);
}
this.dialogRef.close(ResetPasswordDialogResult.Ok);
};
getStrengthScore(result: number) {
this.passwordStrengthScore = result;
}
static open = (dialogService: DialogService, input: DialogConfig<ResetPasswordDialogData>) => {
return dialogService.open<ResetPasswordDialogResult>(ResetPasswordComponent, input);
};
}

View File

@@ -86,10 +86,6 @@ import {
openUserAddEditDialog,
} from "./components/member-dialog";
import { isFixedSeatPlan } from "./components/member-dialog/validators/org-seat-limit-reached.validator";
import {
ResetPasswordComponent,
ResetPasswordDialogResult,
} from "./components/reset-password.component";
import { DeleteManagedMemberWarningService } from "./services/delete-managed-member/delete-managed-member-warning.service";
import { OrganizationUserService } from "./services/organization-user/organization-user.service";
@@ -767,52 +763,32 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
}
async resetPassword(user: OrganizationUserView) {
const changePasswordRefactorFlag = await this.configService.getFeatureFlag(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
);
if (changePasswordRefactorFlag) {
if (!user || !user.email || !user.id) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("orgUserDetailsNotFound"),
});
this.logService.error("Org user details not found when attempting account recovery");
return;
}
const dialogRef = AccountRecoveryDialogComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(user),
email: user.email,
organizationId: this.organization.id as OrganizationId,
organizationUserId: user.id,
},
if (!user || !user.email || !user.id) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("orgUserDetailsNotFound"),
});
const result = await lastValueFrom(dialogRef.closed);
if (result === AccountRecoveryDialogResultType.Ok) {
await this.load();
}
this.logService.error("Org user details not found when attempting account recovery");
return;
}
const dialogRef = ResetPasswordComponent.open(this.dialogService, {
const dialogRef = AccountRecoveryDialogComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(user),
email: user != null ? user.email : null,
email: user.email,
organizationId: this.organization.id as OrganizationId,
id: user != null ? user.id : null,
organizationUserId: user.id,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result === ResetPasswordDialogResult.Ok) {
if (result === AccountRecoveryDialogResultType.Ok) {
await this.load();
}
return;
}
protected async removeUserConfirmationDialog(user: OrganizationUserView) {

View File

@@ -16,7 +16,6 @@ import { BulkRemoveDialogComponent } from "./components/bulk/bulk-remove-dialog.
import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component";
import { BulkStatusComponent } from "./components/bulk/bulk-status.component";
import { UserDialogModule } from "./components/member-dialog";
import { ResetPasswordComponent } from "./components/reset-password.component";
import { MembersRoutingModule } from "./members-routing.module";
import { MembersComponent } from "./members.component";
@@ -39,7 +38,6 @@ import { MembersComponent } from "./members.component";
BulkRestoreRevokeComponent,
BulkStatusComponent,
MembersComponent,
ResetPasswordComponent,
BulkDeleteDialogComponent,
],
})

View File

@@ -3,7 +3,6 @@ export * from "./login";
export * from "./login-decryption-options";
export * from "./webauthn-login";
export * from "./password-management";
export * from "./set-password-jit";
export * from "./registration";
export * from "./two-factor-auth";
export * from "./link-sso.service";

View File

@@ -1,6 +1,5 @@
import { TestBed } from "@angular/core/testing";
import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs";
import { DefaultLoginComponentService } from "@bitwarden/auth/angular";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
@@ -138,8 +137,8 @@ describe("WebLoginComponentService", () => {
resetPasswordPolicyEnabled,
]);
internalPolicyService.masterPasswordPolicyOptions$.mockReturnValue(
of(masterPasswordPolicyOptions),
internalPolicyService.combinePoliciesIntoMasterPasswordPolicyOptions.mockReturnValue(
masterPasswordPolicyOptions,
);
const result = await service.getOrgPoliciesFromOrgInvite();

View File

@@ -2,7 +2,6 @@
// @ts-strict-ignore
import { Injectable } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, switchMap } from "rxjs";
import {
DefaultLoginComponentService,
@@ -11,13 +10,10 @@ import {
} from "@bitwarden/auth/angular";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -99,23 +95,8 @@ export class WebLoginComponentService
const isPolicyAndAutoEnrollEnabled =
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
let enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
if (
await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor)
) {
enforcedPasswordPolicyOptions =
this.policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
} else {
enforcedPasswordPolicyOptions = await firstValueFrom(
this.accountService.activeAccount$.pipe(
getUserId,
switchMap((userId) =>
this.policyService.masterPasswordPolicyOptions$(userId, policies),
),
),
);
}
const enforcedPasswordPolicyOptions =
this.policyService.combinePoliciesIntoMasterPasswordPolicyOptions(policies);
return {
policies,

View File

@@ -1 +0,0 @@
export * from "./web-set-password-jit.service";

View File

@@ -1,27 +0,0 @@
import { inject } from "@angular/core";
import {
DefaultSetPasswordJitService,
SetPasswordCredentials,
SetPasswordJitService,
} from "@bitwarden/auth/angular";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { RouterService } from "../../../../core/router.service";
export class WebSetPasswordJitService
extends DefaultSetPasswordJitService
implements SetPasswordJitService
{
routerService = inject(RouterService);
organizationInviteService = inject(OrganizationInviteService);
override async setPassword(credentials: SetPasswordCredentials) {
await super.setPassword(credentials);
// SSO JIT accepts org invites when setting their MP, meaning
// we can clear the deep linked url for accepting it.
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
}
}

View File

@@ -1,130 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div class="card-body" *ngIf="!syncLoading">
<p
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p>{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<div class="form-group">
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<div class="w-100">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<div>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
<div class="progress-bar invisible"></div>
</div>
</div>
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex">
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button
type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,30 +0,0 @@
import { Component, inject } from "@angular/core";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { RouterService } from "../core";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent {
routerService = inject(RouterService);
organizationInviteService = inject(OrganizationInviteService);
protected override async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString],
): Promise<void> {
await super.onSetPasswordSuccess(masterKey, userKey, keyPair);
// SSO JIT accepts org invites when setting their MP, meaning
// we can clear the deep linked url for accepting it.
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
}
}

View File

@@ -1,129 +0,0 @@
<div class="tabbed-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
class="tw-mb-14"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
autocomplete="new-password"
/>
<bit-hint>
<span class="tw-font-semibold">{{ "important" | i18n }}</span>
{{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }}
</bit-hint>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="checkForBreaches"
name="checkForBreaches"
[(ngModel)]="checkForBreaches"
/>
<label class="form-check-label" for="checkForBreaches">
{{ "checkForBreaches" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="rotateUserKey"
name="RotateUserKey"
[(ngModel)]="rotateUserKey"
(change)="rotateUserKeyClicked()"
/>
<label class="form-check-label" for="rotateUserKey">
{{ "rotateAccountEncKey" | i18n }}
</label>
<a
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'impactOfRotatingYourEncryptionKey' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="form-group">
<label for="masterPasswordHint">{{ "newMasterPassHint" | i18n }}</label>
<input
id="masterPasswordHint"
class="form-control"
maxlength="50"
type="text"
name="MasterPasswordHint"
[(ngModel)]="masterPasswordHint"
/>
</div>
<button type="submit" buttonType="primary" bitButton [loading]="loading">
{{ "changeMasterPassword" | i18n }}
</button>
</form>
<app-webauthn-login-settings></app-webauthn-login-settings>

View File

@@ -1,258 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service";
/**
* @deprecated use the auth `PasswordSettingsComponent` instead
*/
@Component({
selector: "app-change-password",
templateUrl: "change-password.component.html",
standalone: false,
})
export class ChangePasswordComponent
extends BaseChangePasswordComponent
implements OnInit, OnDestroy
{
loading = false;
rotateUserKey = false;
currentMasterPassword: string;
masterPasswordHint: string;
checkForBreaches = true;
characterMinimumMessage = "";
constructor(
private auditService: AuditService,
private cipherService: CipherService,
private keyRotationService: UserKeyRotationService,
private masterPasswordApiService: MasterPasswordApiService,
private router: Router,
private syncService: SyncService,
private userVerificationService: UserVerificationService,
protected accountService: AccountService,
protected dialogService: DialogService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
if (!(await this.userVerificationService.hasMasterPassword())) {
// 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(["/settings/security/two-factor"]);
}
await super.ngOnInit();
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async rotateUserKeyClicked() {
if (this.rotateUserKey) {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
let hasOldAttachments = false;
if (ciphers != null) {
for (let i = 0; i < ciphers.length; i++) {
if (ciphers[i].organizationId == null && ciphers[i].hasOldAttachments) {
hasOldAttachments = true;
break;
}
}
}
if (hasOldAttachments) {
const learnMore = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
content: { key: "oldAttachmentsNeedFixDesc" },
acceptButtonText: { key: "learnMore" },
cancelButtonText: { key: "close" },
type: "warning",
});
if (learnMore) {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/attachments/#add-storage-space",
);
}
this.rotateUserKey = false;
return;
}
const result = await this.dialogService.openSimpleDialog({
title: { key: "rotateEncKeyTitle" },
content:
this.i18nService.t("updateEncryptionKeyWarning") +
" " +
this.i18nService.t("updateEncryptionKeyAccountExportWarning") +
" " +
this.i18nService.t("rotateEncKeyConfirmation"),
type: "warning",
});
if (!result) {
this.rotateUserKey = false;
}
}
}
async submit() {
this.loading = true;
if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
this.loading = false;
return;
}
if (
this.masterPasswordHint != null &&
this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase()
) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("hintEqualsPassword"),
});
this.loading = false;
return;
}
this.leakedPassword = false;
if (this.checkForBreaches) {
this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0;
}
if (!(await this.strongPassword())) {
this.loading = false;
return;
}
try {
if (this.rotateUserKey) {
await this.syncService.fullSync(true);
const user = await firstValueFrom(this.accountService.activeAccount$);
await this.keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
this.currentMasterPassword,
this.masterPassword,
user,
this.masterPasswordHint,
);
} else {
await this.updatePassword(this.masterPassword);
}
} catch (e) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
} finally {
this.loading = false;
}
}
// todo: move this to a service
// https://bitwarden.atlassian.net/browse/PM-17108
private async updatePassword(newMasterPassword: string) {
const currentMasterPassword = this.currentMasterPassword;
const { userId, email } = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => ({ userId: a?.id, email: a?.email }))),
);
const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId));
const currentMasterKey = await this.keyService.makeMasterKey(
currentMasterPassword,
email,
kdfConfig,
);
const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
currentMasterKey,
userId,
);
if (decryptedUserKey == null) {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("invalidMasterPassword"),
});
return;
}
const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig);
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
decryptedUserKey,
);
const request = new PasswordRequest();
request.masterPasswordHash = await this.keyService.hashMasterKey(
this.currentMasterPassword,
currentMasterKey,
);
request.masterPasswordHint = this.masterPasswordHint;
request.newMasterPasswordHash = await this.keyService.hashMasterKey(
newMasterPassword,
newMasterKey,
);
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
try {
await this.masterPasswordApiService.postPassword(request);
this.toastService.showToast({
variant: "success",
message: this.i18nService.t("masterPasswordChanged"),
});
this.messagingService.send("logout");
} catch {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
}
}

View File

@@ -10,8 +10,6 @@ import { OrganizationManagementPreferencesService } from "@bitwarden/common/admi
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -40,10 +38,6 @@ import {
EmergencyAccessTakeoverDialogComponent,
EmergencyAccessTakeoverDialogResultType,
} from "./takeover/emergency-access-takeover-dialog.component";
import {
EmergencyAccessTakeoverComponent,
EmergencyAccessTakeoverResultType,
} from "./takeover/emergency-access-takeover.component";
@Component({
selector: "emergency-access",
@@ -75,7 +69,6 @@ export class EmergencyAccessComponent implements OnInit {
private toastService: ToastService,
private apiService: ApiService,
private accountService: AccountService,
private configService: ConfigService,
) {
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
switchMap((account) =>
@@ -292,60 +285,36 @@ export class EmergencyAccessComponent implements OnInit {
}
takeover = async (details: GrantorEmergencyAccess) => {
const changePasswordRefactorFlag = await this.configService.getFeatureFlag(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
);
if (changePasswordRefactorFlag) {
if (!details || !details.email || !details.id) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("grantorDetailsNotFound"),
});
this.logService.error(
"Grantor details not found when attempting emergency access takeover",
);
return;
}
const grantorName = this.userNamePipe.transform(details);
const dialogRef = EmergencyAccessTakeoverDialogComponent.open(this.dialogService, {
data: {
grantorName,
grantorEmail: details.email,
emergencyAccessId: details.id,
},
if (!details || !details.email || !details.id) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("grantorDetailsNotFound"),
});
const result = await lastValueFrom(dialogRef.closed);
if (result === EmergencyAccessTakeoverDialogResultType.Done) {
this.toastService.showToast({
variant: "success",
title: "",
message: this.i18nService.t("passwordResetFor", grantorName),
});
}
this.logService.error("Grantor details not found when attempting emergency access takeover");
return;
}
const dialogRef = EmergencyAccessTakeoverComponent.open(this.dialogService, {
const grantorName = this.userNamePipe.transform(details);
const dialogRef = EmergencyAccessTakeoverDialogComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(details),
email: details.email,
emergencyAccessId: details.id ?? null,
grantorName,
grantorEmail: details.email,
emergencyAccessId: details.id,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result === EmergencyAccessTakeoverResultType.Done) {
if (result === EmergencyAccessTakeoverDialogResultType.Done) {
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details)),
title: "",
message: this.i18nService.t("passwordResetFor", grantorName),
});
}
return;
};
private removeGrantee(details: GranteeEmergencyAccess) {

View File

@@ -1,54 +0,0 @@
<form [formGroup]="takeoverForm" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle>
{{ "takeover" | i18n }}
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
</span>
<div bitDialogContent>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<div class="tw-w-full tw-flex tw-gap-4">
<div class="tw-relative tw-flex-1">
<bit-form-field disableMargin class="tw-mb-2">
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
autocomplete="new-password"
formControlName="masterPassword"
/>
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
<app-password-strength
[password]="takeoverForm.value.masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<div class="tw-relative tw-flex-1">
<bit-form-field disableMargin class="tw-mb-2">
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
autocomplete="new-password"
formControlName="masterPasswordRetype"
/>
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
</div>
</div>
</div>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "save" | i18n }}
</button>
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -1,145 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { switchMap, takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
DialogConfig,
DialogRef,
DIALOG_DATA,
DialogService,
ToastService,
} from "@bitwarden/components";
import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { EmergencyAccessService } from "../../../emergency-access";
// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum EmergencyAccessTakeoverResultType {
Done = "done",
}
type EmergencyAccessTakeoverDialogData = {
/** display name of the account requesting emergency access takeover */
name: string;
/** email of the account requesting emergency access takeover */
email: string;
/** traces a unique emergency request */
emergencyAccessId: string;
};
@Component({
selector: "emergency-access-takeover",
templateUrl: "emergency-access-takeover.component.html",
standalone: false,
})
export class EmergencyAccessTakeoverComponent
extends ChangePasswordComponent
implements OnInit, OnDestroy
{
@Input() kdf: KdfType;
@Input() kdfIterations: number;
takeoverForm = this.formBuilder.group({
masterPassword: ["", [Validators.required]],
masterPasswordRetype: ["", [Validators.required]],
});
constructor(
@Inject(DIALOG_DATA) protected params: EmergencyAccessTakeoverDialogData,
private formBuilder: FormBuilder,
i18nService: I18nService,
keyService: KeyService,
messagingService: MessagingService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
private emergencyAccessService: EmergencyAccessService,
private logService: LogService,
dialogService: DialogService,
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
kdfConfigService: KdfConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
protected toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
const policies = await this.emergencyAccessService.getGrantorPolicies(
this.params.emergencyAccessId,
);
this.accountService.activeAccount$
.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
takeUntil(this.destroy$),
)
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
}
ngOnDestroy(): void {
super.ngOnDestroy();
}
submit = async () => {
if (this.takeoverForm.invalid) {
this.takeoverForm.markAllAsTouched();
return;
}
this.masterPassword = this.takeoverForm.get("masterPassword").value;
this.masterPasswordRetype = this.takeoverForm.get("masterPasswordRetype").value;
if (!(await this.strongPassword())) {
return;
}
try {
await this.emergencyAccessService.takeover(
this.params.emergencyAccessId,
this.masterPassword,
this.params.email,
);
} catch (e) {
this.logService.error(e);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("unexpectedError"),
});
}
this.dialogRef.close(EmergencyAccessTakeoverResultType.Done);
};
/**
* Strongly typed helper to open a EmergencyAccessTakeoverComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
static open = (
dialogService: DialogService,
config: DialogConfig<EmergencyAccessTakeoverDialogData>,
) => {
return dialogService.open<EmergencyAccessTakeoverResultType>(
EmergencyAccessTakeoverComponent,
config,
);
};
}

View File

@@ -2,11 +2,9 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { DeviceManagementComponent } from "@bitwarden/angular/auth/device-management/device-management.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ChangePasswordComponent } from "../change-password.component";
import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component";
import { DeviceManagementOldComponent } from "./device-management-old.component";
@@ -21,30 +19,9 @@ const routes: Routes = [
data: { titleId: "security" },
children: [
{ path: "", pathMatch: "full", redirectTo: "password" },
{
path: "change-password",
component: ChangePasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"/settings/security/password",
false,
),
],
data: { titleId: "masterPassword" },
},
{
path: "password",
component: PasswordSettingsComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
true,
"/settings/security/change-password",
false,
),
],
data: { titleId: "masterPassword" },
},
{

View File

@@ -1,8 +1,6 @@
import { Component, OnInit } from "@angular/core";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { HeaderModule } from "../../../layouts/header/header.module";
import { SharedModule } from "../../../shared";
@@ -13,21 +11,11 @@ import { SharedModule } from "../../../shared";
})
export class SecurityComponent implements OnInit {
showChangePassword = true;
changePasswordRoute = "change-password";
changePasswordRoute = "password";
constructor(
private userVerificationService: UserVerificationService,
private configService: ConfigService,
) {}
constructor(private userVerificationService: UserVerificationService) {}
async ngOnInit() {
this.showChangePassword = await this.userVerificationService.hasMasterPassword();
const changePasswordRefreshFlag = await this.configService.getFeatureFlag(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
);
if (changePasswordRefreshFlag) {
this.changePasswordRoute = "password";
}
}
}

View File

@@ -6,7 +6,6 @@ import { UserKeyRotationModule } from "../../key-management/key-rotation/user-ke
import { SharedModule } from "../../shared";
import { EmergencyAccessModule } from "../emergency-access";
import { ChangePasswordComponent } from "./change-password.component";
import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
@NgModule({
@@ -17,8 +16,8 @@ import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
PasswordCalloutComponent,
UserKeyRotationModule,
],
declarations: [ChangePasswordComponent],
declarations: [],
providers: [],
exports: [ChangePasswordComponent],
exports: [],
})
export class AuthSettingsModule {}

View File

@@ -1,90 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{ "masterPasswordInvalidWarning" | i18n }} </app-callout>
<auth-password-callout
[policy]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
></auth-password-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
autocomplete="new-password"
/>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
></app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>
<button (click)="cancel()" type="button" class="btn btn-outline-secondary">
<span>{{ "cancel" | i18n }}</span>
</button>
</form>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,24 +0,0 @@
import { Component, inject } from "@angular/core";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { RouterService } from "../core";
@Component({
selector: "app-update-password",
templateUrl: "update-password.component.html",
standalone: false,
})
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
private routerService = inject(RouterService);
private organizationInviteService = inject(OrganizationInviteService);
override async cancel() {
// clearing the login redirect url so that the user
// does not join the organization if they cancel
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
await super.cancel();
}
}

View File

@@ -1,96 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="tw-mt-12 tw-flex tw-justify-center">
<div class="tw-w-1/3">
<h1 bitTypography="h1" class="tw-mb-4 tw-text-center">{{ "updateMasterPassword" | i18n }}</h1>
<div
class="tw-block tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8"
>
<app-callout type="warning">{{ masterPasswordWarningText }} </app-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<bit-form-field *ngIf="requireCurrentPassword">
<bit-label>{{ "currentMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="verification.secret"
name="currentMasterPassword"
id="currentMasterPassword"
[appAutofocus]="requireCurrentPassword"
/>
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
<div class="tw-mb-4">
<bit-form-field class="!tw-mb-1">
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="masterPassword"
name="masterPassword"
id="masterPassword"
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
</bit-form-field>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="masterPasswordRetype"
name="masterPasswordRetype"
id="masterPasswordRetype"
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "masterPassHint" | i18n }}</bit-label>
<input bitInput type="text" [(ngModel)]="hint" name="hint" id="hint" />
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
</bit-form-field>
<hr />
<div class="tw-flex tw-space-x-2">
<button
type="submit"
bitButton
[block]="true"
buttonType="primary"
[loading]="form.loading"
[disabled]="form.loading"
>
{{ "submit" | i18n }}
</button>
<button type="button" bitButton [block]="true" buttonType="secondary" (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}

View File

@@ -33,7 +33,6 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.
import {
RegistrationFinishService as RegistrationFinishServiceAbstraction,
LoginComponentService,
SetPasswordJitService,
SsoComponentService,
LoginDecryptionOptionsService,
TwoFactorAuthDuoComponentService,
@@ -117,7 +116,6 @@ import { flagEnabled } from "../../utils/flags";
import { PolicyListService } from "../admin-console/core/policy-list.service";
import {
WebChangePasswordService,
WebSetPasswordJitService,
WebRegistrationFinishService,
WebLoginComponentService,
WebLoginDecryptionOptionsService,
@@ -277,21 +275,6 @@ const safeProviders: SafeProvider[] = [
useClass: WebLockComponentService,
deps: [],
}),
safeProvider({
provide: SetPasswordJitService,
useClass: WebSetPasswordJitService,
deps: [
EncryptService,
I18nServiceAbstraction,
KdfConfigService,
KeyServiceAbstraction,
MasterPasswordApiService,
InternalMasterPasswordServiceAbstraction,
OrganizationApiServiceAbstraction,
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
],
}),
safeProvider({
provide: SetInitialPasswordService,
useClass: WebSetInitialPasswordService,

View File

@@ -12,14 +12,12 @@ import {
} from "@bitwarden/angular/auth/guards";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
PasswordHintComponent,
RegistrationFinishComponent,
RegistrationStartComponent,
RegistrationStartSecondaryComponent,
RegistrationStartSecondaryComponentData,
SetPasswordJitComponent,
RegistrationLinkExpiredComponent,
LoginComponent,
LoginSecondaryContentComponent,
@@ -39,7 +37,6 @@ import {
NewDeviceVerificationComponent,
DeviceVerificationIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, Icons } from "@bitwarden/components";
import { LockComponent } from "@bitwarden/key-management-ui";
import { VaultIcons } from "@bitwarden/vault";
@@ -55,13 +52,10 @@ import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login
import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component";
import { RecoverDeleteComponent } from "./auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component";
import { SetPasswordComponent } from "./auth/set-password.component";
import { AccountComponent } from "./auth/settings/account/account.component";
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component";
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component";
import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component";
@@ -115,11 +109,6 @@ const routes: Routes = [
component: LoginViaWebAuthnComponent,
data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties,
},
{
path: "set-password",
component: SetPasswordComponent,
data: { titleId: "setMasterPassword" } satisfies RouteDataProperties,
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
path: "accept-organization",
@@ -143,34 +132,6 @@ const routes: Routes = [
canActivate: [unauthGuardFn()],
data: { titleId: "deleteOrganization" },
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"change-password",
false,
),
authGuard,
],
data: { titleId: "updateTempPassword" } satisfies RouteDataProperties,
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"change-password",
false,
),
authGuard,
],
data: { titleId: "updatePassword" } satisfies RouteDataProperties,
},
],
},
{
@@ -329,24 +290,12 @@ const routes: Routes = [
},
{
path: "set-initial-password",
canActivate: [canAccessFeature(FeatureFlag.PM16117_SetInitialPasswordRefactor), authGuard],
canActivate: [authGuard],
component: SetInitialPasswordComponent,
data: {
maxWidth: "lg",
} satisfies AnonLayoutWrapperData,
},
{
path: "set-password-jit",
component: SetPasswordJitComponent,
data: {
pageTitle: {
key: "joinOrganization",
},
pageSubtitle: {
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
},
} satisfies AnonLayoutWrapperData,
},
{
path: "signup-link-expired",
canActivate: [unauthGuardFn()],
@@ -601,10 +550,7 @@ const routes: Routes = [
{
path: "change-password",
component: ChangePasswordComponent,
canActivate: [
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
authGuard,
],
canActivate: [authGuard],
},
{
path: "setup-extension",

View File

@@ -14,16 +14,12 @@ import { VerifyRecoverDeleteOrgComponent } from "../admin-console/organizations/
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component";
import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/takeover/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/view/emergency-access-view.component";
import { UserVerificationModule } from "../auth/shared/components/user-verification";
import { UpdatePasswordComponent } from "../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component";
@@ -73,7 +69,6 @@ import { SharedModule } from "./shared.module";
EmergencyAccessAddEditComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
OrgEventsComponent,
OrgExposedPasswordsReportComponent,
@@ -85,12 +80,9 @@ import { SharedModule } from "./shared.module";
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RemovePasswordComponent,
SetPasswordComponent,
SponsoredFamiliesComponent,
FreeBitwardenFamiliesComponent,
SponsoringOrgRowComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
],
@@ -100,7 +92,6 @@ import { SharedModule } from "./shared.module";
EmergencyAccessAddEditComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
OrganizationLayoutComponent,
OrgEventsComponent,
@@ -114,12 +105,9 @@ import { SharedModule } from "./shared.module";
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RemovePasswordComponent,
SetPasswordComponent,
SponsoredFamiliesComponent,
FreeBitwardenFamiliesComponent,
SponsoringOrgRowComponent,
UpdateTempPasswordComponent,
UpdatePasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
HeaderModule,