diff --git a/apps/browser/src/auth/popup/set-password.component.html b/apps/browser/src/auth/popup/set-password.component.html deleted file mode 100644 index 71a2e3ac588..00000000000 --- a/apps/browser/src/auth/popup/set-password.component.html +++ /dev/null @@ -1,160 +0,0 @@ -
-
-
- -
-

- {{ "setMasterPassword" | i18n }} -

-
- -
-
-
-
- -
-
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - -
-
-
-
-
-
- - -
-
- -
-
- - - -
-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- -
-
-
-
diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts deleted file mode 100644 index 2a796854531..00000000000 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ /dev/null @@ -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 {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.html b/apps/browser/src/auth/popup/update-temp-password.component.html deleted file mode 100644 index 0ce82aa20cf..00000000000 --- a/apps/browser/src/auth/popup/update-temp-password.component.html +++ /dev/null @@ -1,142 +0,0 @@ -
-
-
- -
-

- {{ "updateMasterPassword" | i18n }} -

-
- -
-
-
- - {{ masterPasswordWarningText }} - - - -
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
-
-
diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts deleted file mode 100644 index e8cf64b7548..00000000000 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ /dev/null @@ -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 = 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(["/"]); - } - } -} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 52a60d9c23d..f01809433e3 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -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], }, ], }, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 77c87838ff7..687ae67d43c 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -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, diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index db3e69f7d6f..30ae906c98d 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -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], }, ], }, diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 112732d8f2c..79c1aae0c3b 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -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], diff --git a/apps/desktop/src/app/services/desktop-set-password-jit.service.ts b/apps/desktop/src/app/services/desktop-set-password-jit.service.ts deleted file mode 100644 index f6ea3d0ce84..00000000000 --- a/apps/desktop/src/app/services/desktop-set-password-jit.service.ts +++ /dev/null @@ -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"); - } -} diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 04c989c4f36..c3b88077dbb 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -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, diff --git a/apps/desktop/src/auth/set-password.component.html b/apps/desktop/src/auth/set-password.component.html deleted file mode 100644 index 46d954327f8..00000000000 --- a/apps/desktop/src/auth/set-password.component.html +++ /dev/null @@ -1,169 +0,0 @@ -
-
- Bitwarden -

{{ "setMasterPassword" | i18n }}

-
- - {{ "loading" | i18n }} -
-
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - -
- -
-
-
-
-
- - -
-
- -
-
- - -
-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
- -
-
- diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts deleted file mode 100644 index d45fc111a97..00000000000 --- a/apps/desktop/src/auth/set-password.component.ts +++ /dev/null @@ -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 { - await super.onSetPasswordSuccess(masterKey, userKey, keyPair); - this.messagingService.send("redrawMenu"); - } -} diff --git a/apps/desktop/src/auth/update-temp-password.component.html b/apps/desktop/src/auth/update-temp-password.component.html deleted file mode 100644 index 11b8ad4361b..00000000000 --- a/apps/desktop/src/auth/update-temp-password.component.html +++ /dev/null @@ -1,136 +0,0 @@ -
-
- - {{ masterPasswordWarningText }} - - - -
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
diff --git a/apps/desktop/src/auth/update-temp-password.component.ts b/apps/desktop/src/auth/update-temp-password.component.ts deleted file mode 100644 index ead10660b92..00000000000 --- a/apps/desktop/src/auth/update-temp-password.component.ts +++ /dev/null @@ -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 {} diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss index 4098ad860dd..73ef7554f6a 100644 --- a/apps/desktop/src/scss/pages.scss +++ b/apps/desktop/src/scss/pages.scss @@ -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; - } - } - } -} diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html deleted file mode 100644 index b5f50841e13..00000000000 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.html +++ /dev/null @@ -1,67 +0,0 @@ -
- - - {{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }} - - - - - - {{ "newPassword" | i18n }} - - - - - - - - - - - - - - -
diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts deleted file mode 100644 index 961d5482d8a..00000000000 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ /dev/null @@ -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(); - - 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, - 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) => { - return dialogService.open(ResetPasswordComponent, input); - }; -} diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 77a0cecce8e..2a84efd3320 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -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 } 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) { diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts index 5f626d44161..d9c5ae356a2 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.module.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts @@ -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, ], }) diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index 8c556986225..02f13cd436b 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -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"; diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts index 4cc06baf32b..799e10bc15c 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts @@ -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(); diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index cf0adb91144..4ee84ecfde2 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -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, diff --git a/apps/web/src/app/auth/core/services/set-password-jit/index.ts b/apps/web/src/app/auth/core/services/set-password-jit/index.ts deleted file mode 100644 index fc119fd964f..00000000000 --- a/apps/web/src/app/auth/core/services/set-password-jit/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./web-set-password-jit.service"; diff --git a/apps/web/src/app/auth/core/services/set-password-jit/web-set-password-jit.service.ts b/apps/web/src/app/auth/core/services/set-password-jit/web-set-password-jit.service.ts deleted file mode 100644 index 3078b8e3b83..00000000000 --- a/apps/web/src/app/auth/core/services/set-password-jit/web-set-password-jit.service.ts +++ /dev/null @@ -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(); - } -} diff --git a/apps/web/src/app/auth/set-password.component.html b/apps/web/src/app/auth/set-password.component.html deleted file mode 100644 index 252893d22cb..00000000000 --- a/apps/web/src/app/auth/set-password.component.html +++ /dev/null @@ -1,130 +0,0 @@ -
-
-
-

{{ "setMasterPassword" | i18n }}

-
-
- - {{ "loading" | i18n }} -
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - -
- - - -
-
- - - -
-
- - -
-
- {{ "masterPassDesc" | i18n }} -
-
- -
- - -
-
-
- - - {{ "masterPassHintDesc" | i18n }} -
-
-
- - -
-
-
-
-
-
diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts deleted file mode 100644 index a2044e298a5..00000000000 --- a/apps/web/src/app/auth/set-password.component.ts +++ /dev/null @@ -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 { - 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(); - } -} diff --git a/apps/web/src/app/auth/settings/change-password.component.html b/apps/web/src/app/auth/settings/change-password.component.html deleted file mode 100644 index 34bb74ee473..00000000000 --- a/apps/web/src/app/auth/settings/change-password.component.html +++ /dev/null @@ -1,129 +0,0 @@ -
-

{{ "changeMasterPassword" | i18n }}

-
- -{{ "loggedOutWarning" | i18n }} - - - -
-
-
-
- - -
-
-
-
-
-
- - - - {{ "important" | i18n }} - {{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }} - - - -
-
-
-
- - -
-
-
-
-
- - -
-
-
-
- - - - - -
-
-
- - -
- -
- - diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts deleted file mode 100644 index ce10a0e5a34..00000000000 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ /dev/null @@ -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"), - }); - } - } -} diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 1d78bb7dd17..bc4bfc5ef1d 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -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) { diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html deleted file mode 100644 index 64b35344455..00000000000 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - {{ "takeover" | i18n }} - {{ params.name }} - -
- {{ "loggedOutWarning" | i18n }} - - -
-
- - {{ "newMasterPass" | i18n }} - - - - - -
-
- - {{ "confirmNewMasterPass" | i18n }} - - - -
-
-
- - - - -
-
diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts deleted file mode 100644 index ede60887725..00000000000 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ /dev/null @@ -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, - 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, - ) => { - return dialogService.open( - EmergencyAccessTakeoverComponent, - config, - ); - }; -} diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index 2ec1be5cb7f..f7586329380 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -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" }, }, { diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 2240371637d..2a237bf6d01 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -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"; - } } } diff --git a/apps/web/src/app/auth/settings/settings.module.ts b/apps/web/src/app/auth/settings/settings.module.ts index 437711f4aa6..fcab9450e1c 100644 --- a/apps/web/src/app/auth/settings/settings.module.ts +++ b/apps/web/src/app/auth/settings/settings.module.ts @@ -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 {} diff --git a/apps/web/src/app/auth/update-password.component.html b/apps/web/src/app/auth/update-password.component.html deleted file mode 100644 index 7fb3e1fa491..00000000000 --- a/apps/web/src/app/auth/update-password.component.html +++ /dev/null @@ -1,90 +0,0 @@ -
-
-
-

{{ "updateMasterPassword" | i18n }}

-
-
- {{ "masterPasswordInvalidWarning" | i18n }} - - - -
-
-
- - -
-
-
-
-
-
- - - -
-
-
-
- - -
-
-
- - - -
-
-
-
- diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts deleted file mode 100644 index bc53f824228..00000000000 --- a/apps/web/src/app/auth/update-password.component.ts +++ /dev/null @@ -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(); - } -} diff --git a/apps/web/src/app/auth/update-temp-password.component.html b/apps/web/src/app/auth/update-temp-password.component.html deleted file mode 100644 index 4fd0ea72b5f..00000000000 --- a/apps/web/src/app/auth/update-temp-password.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
-
-
-

{{ "updateMasterPassword" | i18n }}

-
- {{ masterPasswordWarningText }} - - - - {{ "currentMasterPass" | i18n }} - - - -
- - {{ "newMasterPass" | i18n }} - - - - - -
- - {{ "confirmNewMasterPass" | i18n }} - - - - - {{ "masterPassHint" | i18n }} - - {{ "masterPassHintDesc" | i18n }} - -
-
- - -
-
-
-
-
diff --git a/apps/web/src/app/auth/update-temp-password.component.ts b/apps/web/src/app/auth/update-temp-password.component.ts deleted file mode 100644 index ead10660b92..00000000000 --- a/apps/web/src/app/auth/update-temp-password.component.ts +++ /dev/null @@ -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 {} diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 7fe8ef4c79f..2721f1f7add 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -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, diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 1fb19757d60..4c1ed1a7472 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -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", diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 97c3fa0375c..4de217f331b 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -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, diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts deleted file mode 100644 index 8a564d68fd4..00000000000 --- a/libs/angular/src/auth/components/change-password.component.ts +++ /dev/null @@ -1,232 +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 { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs"; - -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 { 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 { 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 { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserKey, MasterKey } from "@bitwarden/common/types/key"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { PasswordColorText } from "../../tools/password-strength/password-strength.component"; - -@Directive() -export class ChangePasswordComponent implements OnInit, OnDestroy { - masterPassword: string; - masterPasswordRetype: string; - formPromise: Promise; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - passwordStrengthResult: any; - color: string; - text: string; - leakedPassword: boolean; - minimumLength = Utils.minimumPasswordLength; - - protected email: string; - protected kdfConfig: KdfConfig; - - protected destroy$ = new Subject(); - - constructor( - 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, - ) {} - - async ngOnInit() { - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), - takeUntil(this.destroy$), - ) - .subscribe( - (enforcedPasswordPolicyOptions) => - (this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions), - ); - - if (this.enforcedPolicyOptions?.minLength) { - this.minimumLength = this.enforcedPolicyOptions.minLength; - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit() { - if (!(await this.strongPassword())) { - return; - } - - if (!(await this.setupSubmitActions())) { - return; - } - - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - - if (this.kdfConfig == null) { - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - } - - // Create new master key - const newMasterKey = await this.keyService.makeMasterKey( - this.masterPassword, - email.trim().toLowerCase(), - this.kdfConfig, - ); - const newMasterKeyHash = await this.keyService.hashMasterKey(this.masterPassword, newMasterKey); - - let newProtectedUserKey: [UserKey, EncString] = null; - const userKey = await this.keyService.getUserKey(); - if (userKey == null) { - newProtectedUserKey = await this.keyService.makeUserKey(newMasterKey); - } else { - newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey); - } - - await this.performSubmitActions(newMasterKeyHash, newMasterKey, newProtectedUserKey); - } - - async setupSubmitActions(): Promise { - // Override in sub-class - // Can be used for additional validation and/or other processes the should occur before changing passwords - return true; - } - - async performSubmitActions( - newMasterKeyHash: string, - newMasterKey: MasterKey, - newUserKey: [UserKey, EncString], - ) { - // Override in sub-class - } - - async strongPassword(): Promise { - if (this.masterPassword == null || this.masterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - if (this.masterPassword.length < this.minimumLength) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordMinimumlength", this.minimumLength), - }); - return false; - } - if (this.masterPassword !== this.masterPasswordRetype) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPassDoesntMatch"), - }); - return false; - } - - const strengthResult = this.passwordStrengthResult; - - if ( - this.enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - strengthResult.score, - this.masterPassword, - this.enforcedPolicyOptions, - ) - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"), - }); - return false; - } - - const weakPassword = strengthResult != null && strengthResult.score < 3; - - if (weakPassword && this.leakedPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "weakAndExposedMasterPassword" }, - content: { key: "weakAndBreachedMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } else { - if (weakPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "weakMasterPassword" }, - content: { key: "weakMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } - if (this.leakedPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "exposedMasterPassword" }, - content: { key: "exposedMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } - } - - return true; - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - acceptButtonText: { key: "logOut" }, - type: "warning", - }); - - if (confirmed) { - this.messagingService.send("logout"); - } - } - - getStrengthResult(result: any) { - this.passwordStrengthResult = result; - } - - getPasswordScoreText(event: PasswordColorText) { - this.color = event.color; - this.text = event.text; - } -} diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts deleted file mode 100644 index 03cbdbc625a..00000000000 --- a/libs/angular/src/auth/components/set-password.component.ts +++ /dev/null @@ -1,300 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, of } from "rxjs"; -import { filter, first, switchMap, tap } from "rxjs/operators"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { - OrganizationUserApiService, - OrganizationUserResetPasswordEnrollmentRequest, -} from "@bitwarden/admin-console/common"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response"; -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 { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; -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 { 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 { HashPurpose } from "@bitwarden/common/platform/enums"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -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 { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class SetPasswordComponent extends BaseChangePasswordComponent implements OnInit { - syncLoading = true; - showPassword = false; - hint = ""; - orgSsoIdentifier: string = null; - orgId: string; - resetPasswordAutoEnroll = false; - onSuccessfulChangePassword: () => Promise; - successRoute = "vault"; - activeUserId: UserId; - - forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; - ForceSetPasswordReason = ForceSetPasswordReason; - - 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, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - // 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 - super.ngOnInit(); - - await this.syncService.fullSync(true); - this.syncLoading = false; - - this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - - this.forceSetPasswordReason = await firstValueFrom( - this.masterPasswordService.forceSetPasswordReason$(this.activeUserId), - ); - - this.route.queryParams - .pipe( - first(), - switchMap((qParams) => { - if (qParams.identifier != null) { - return of(qParams.identifier); - } else { - // Try to get orgSsoId from state as fallback - // Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario. - return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeUserId); - } - }), - filter((orgSsoId) => orgSsoId != null), - tap((orgSsoId: string) => { - this.orgSsoIdentifier = orgSsoId; - }), - switchMap((orgSsoId: string) => this.organizationApiService.getAutoEnrollStatus(orgSsoId)), - tap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) => { - this.orgId = orgAutoEnrollStatusResponse.id; - this.resetPasswordAutoEnroll = orgAutoEnrollStatusResponse.resetPasswordEnabled; - }), - switchMap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) => - // Must get org id from response to get master password policy options - this.policyApiService.getMasterPasswordPolicyOptsForOrgUser( - orgAutoEnrollStatusResponse.id, - ), - ), - tap((masterPasswordPolicyOptions: MasterPasswordPolicyOptions) => { - this.enforcedPolicyOptions = masterPasswordPolicyOptions; - }), - ) - .subscribe({ - error: () => { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - }, - }); - } - - async setupSubmitActions() { - this.kdfConfig = DEFAULT_KDF_CONFIG; - return true; - } - - async performSubmitActions( - masterPasswordHash: string, - masterKey: MasterKey, - userKey: [UserKey, EncString], - ) { - let keysRequest: KeysRequest | null = null; - let newKeyPair: [string, EncString] | null = null; - - if ( - this.forceSetPasswordReason != - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission - ) { - // Existing JIT provisioned user in a MP encryption org setting first password - // Users in this state will not already have a user asymmetric key pair so must create it for them - // We don't want to re-create the user key pair if the user already has one (TDE user case) - - // in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one - const existingUserPrivateKey = (await firstValueFrom( - this.keyService.userPrivateKey$(this.activeUserId), - )) as Uint8Array; - const existingUserPublicKey = await firstValueFrom( - this.keyService.userPublicKey$(this.activeUserId), - ); - if (existingUserPrivateKey != null && existingUserPublicKey != null) { - const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey); - newKeyPair = [ - existingUserPublicKeyB64, - await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey[0]), - ]; - } else { - newKeyPair = await this.keyService.makeKeyPair(userKey[0]); - } - keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString); - } - - const request = new SetPasswordRequest( - masterPasswordHash, - userKey[1].encryptedString, - this.hint, - this.orgSsoIdentifier, - keysRequest, - this.kdfConfig.kdfType, //always PBKDF2 --> see this.setupSubmitActions - this.kdfConfig.iterations, - ); - try { - if (this.resetPasswordAutoEnroll) { - this.formPromise = this.masterPasswordApiService - .setPassword(request) - .then(async () => { - await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair); - return this.organizationApiService.getKeys(this.orgId); - }) - .then(async (response) => { - if (response == null) { - throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } - const publicKey = Utils.fromB64ToArray(response.publicKey); - - // RSA Encrypt user key with organization public key - const userKey = await this.keyService.getUserKey(); - const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( - userKey, - publicKey, - ); - - const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); - resetRequest.masterPasswordHash = masterPasswordHash; - resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; - - return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( - this.orgId, - this.activeUserId, - resetRequest, - ); - }); - } else { - this.formPromise = this.masterPasswordApiService.setPassword(request).then(async () => { - await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair); - }); - } - - await this.formPromise; - - if (this.onSuccessfulChangePassword != null) { - // 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.onSuccessfulChangePassword(); - } else { - // 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([this.successRoute]); - } - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - protected async onSetPasswordSuccess( - masterKey: MasterKey, - userKey: [UserKey, EncString], - keyPair: [string, EncString] | null, - ) { - // Clear force set password reason to allow navigation back to vault. - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.None, - this.activeUserId, - ); - - // User now has a password so update account decryption options in state - const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - userDecryptionOpts.hasMasterPassword = true; - await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); - await this.kdfConfigService.setKdfConfig(this.activeUserId, this.kdfConfig); - await this.masterPasswordService.setMasterKey(masterKey, this.activeUserId); - await this.keyService.setUserKey(userKey[0], this.activeUserId); - - // Set private key only for new JIT provisioned users in MP encryption orgs - // Existing TDE users will have private key set on sync or on login - if ( - keyPair !== null && - this.forceSetPasswordReason != - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission - ) { - await this.keyService.setPrivateKey(keyPair[1].encryptedString, this.activeUserId); - } - - const localMasterKeyHash = await this.keyService.hashMasterKey( - this.masterPassword, - masterKey, - HashPurpose.LocalAuthorization, - ); - await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.activeUserId); - } -} diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts deleted file mode 100644 index fa3ab58db69..00000000000 --- a/libs/angular/src/auth/components/update-password.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -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 { 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 { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { Verification } from "@bitwarden/common/auth/types/verification"; -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 { 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 { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class UpdatePasswordComponent extends BaseChangePasswordComponent { - hint: string; - key: string; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - showPassword = false; - currentMasterPassword: string; - - onSuccessfulChangePassword: () => Promise; - - constructor( - protected router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - keyService: KeyService, - messagingService: MessagingService, - private masterPasswordApiService: MasterPasswordApiService, - private userVerificationService: UserVerificationService, - private logService: LogService, - dialogService: DialogService, - kdfConfigService: KdfConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - async cancel() { - await this.router.navigate(["/vault"]); - } - - async setupSubmitActions(): Promise { - if (this.currentMasterPassword == null || this.currentMasterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - - const secret: Verification = { - type: VerificationType.MasterPassword, - secret: this.currentMasterPassword, - }; - try { - await this.userVerificationService.verifyUser(secret); - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e.message, - }); - return false; - } - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - return true; - } - - async performSubmitActions( - newMasterKeyHash: string, - newMasterKey: MasterKey, - newUserKey: [UserKey, EncString], - ) { - try { - // Create Request - const request = new PasswordRequest(); - request.masterPasswordHash = await this.keyService.hashMasterKey( - this.currentMasterPassword, - await this.keyService.getOrDeriveMasterKey(this.currentMasterPassword), - ); - request.newMasterPasswordHash = newMasterKeyHash; - request.key = newUserKey[1].encryptedString; - - // Update user's password - await this.masterPasswordApiService.postPassword(request); - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("masterPasswordChanged"), - message: this.i18nService.t("logBackIn"), - }); - - if (this.onSuccessfulChangePassword != null) { - // 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.onSuccessfulChangePassword(); - } else { - this.messagingService.send("logout"); - } - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts deleted file mode 100644 index 681f69b083a..00000000000 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; - -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 { 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 { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; -import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; -import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; -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 { 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 { 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"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class UpdateTempPasswordComponent extends BaseChangePasswordComponent implements OnInit { - hint: string; - key: string; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - showPassword = false; - reason: ForceSetPasswordReason = ForceSetPasswordReason.None; - verification: MasterPasswordVerification = { - type: VerificationType.MasterPassword, - secret: "", - }; - - onSuccessfulChangePassword: () => Promise; - - get requireCurrentPassword(): boolean { - return this.reason === ForceSetPasswordReason.WeakMasterPassword; - } - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - keyService: KeyService, - messagingService: MessagingService, - private masterPasswordApiService: MasterPasswordApiService, - private syncService: SyncService, - private logService: LogService, - private userVerificationService: UserVerificationService, - protected router: Router, - dialogService: DialogService, - kdfConfigService: KdfConfigService, - accountService: AccountService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - await this.syncService.fullSync(true); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - this.reason = await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId)); - - // If we somehow end up here without a reason, go back to the home page - if (this.reason == ForceSetPasswordReason.None) { - // 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(["/"]); - return; - } - - await super.ngOnInit(); - } - - get masterPasswordWarningText(): string { - if (this.reason == ForceSetPasswordReason.WeakMasterPassword) { - return this.i18nService.t("updateWeakMasterPasswordWarning"); - } else if (this.reason == ForceSetPasswordReason.TdeOffboarding) { - return this.i18nService.t("tdeDisabledMasterPasswordRequired"); - } else { - return this.i18nService.t("updateMasterPasswordWarning"); - } - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - async setupSubmitActions(): Promise { - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - this.email = email; - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - return true; - } - - async submit() { - // Validation - if (!(await this.strongPassword())) { - return; - } - - if (!(await this.setupSubmitActions())) { - return; - } - - try { - // Create new key and hash new password - const newMasterKey = await this.keyService.makeMasterKey( - this.masterPassword, - this.email.trim().toLowerCase(), - this.kdfConfig, - ); - const newPasswordHash = await this.keyService.hashMasterKey( - this.masterPassword, - newMasterKey, - ); - - // Grab user key - const userKey = await this.keyService.getUserKey(); - - // Encrypt user key with new master key - const newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, - userKey, - ); - - await this.performSubmitActions(newPasswordHash, newMasterKey, newProtectedUserKey); - } catch (e) { - this.logService.error(e); - } - } - - async performSubmitActions( - masterPasswordHash: string, - masterKey: MasterKey, - userKey: [UserKey, EncString], - ) { - try { - switch (this.reason) { - case ForceSetPasswordReason.AdminForcePasswordReset: - this.formPromise = this.updateTempPassword(masterPasswordHash, userKey); - break; - case ForceSetPasswordReason.WeakMasterPassword: - this.formPromise = this.updatePassword(masterPasswordHash, userKey); - break; - case ForceSetPasswordReason.TdeOffboarding: - this.formPromise = this.updateTdeOffboardingPassword(masterPasswordHash, userKey); - break; - } - - await this.formPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedMasterPassword"), - }); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.None, - userId, - ); - - if (this.onSuccessfulChangePassword != null) { - // 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.onSuccessfulChangePassword(); - } else { - this.messagingService.send("logout"); - } - } catch (e) { - this.logService.error(e); - } - } - private async updateTempPassword(masterPasswordHash: string, userKey: [UserKey, EncString]) { - const request = new UpdateTempPasswordRequest(); - request.key = userKey[1].encryptedString; - request.newMasterPasswordHash = masterPasswordHash; - request.masterPasswordHint = this.hint; - - return this.masterPasswordApiService.putUpdateTempPassword(request); - } - - private async updatePassword(newMasterPasswordHash: string, userKey: [UserKey, EncString]) { - const request = await this.userVerificationService.buildRequest( - this.verification, - PasswordRequest, - ); - request.masterPasswordHint = this.hint; - request.newMasterPasswordHash = newMasterPasswordHash; - request.key = userKey[1].encryptedString; - - return this.masterPasswordApiService.postPassword(request); - } - - private async updateTdeOffboardingPassword( - masterPasswordHash: string, - userKey: [UserKey, EncString], - ) { - const request = new UpdateTdeOffboardingPasswordRequest(); - request.key = userKey[1].encryptedString; - request.newMasterPasswordHash = masterPasswordHash; - request.masterPasswordHint = this.hint; - - return this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request); - } -} diff --git a/libs/angular/src/auth/guards/auth.guard.spec.ts b/libs/angular/src/auth/guards/auth.guard.spec.ts index 1681fa2b4ea..fccfcd58874 100644 --- a/libs/angular/src/auth/guards/auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/auth.guard.spec.ts @@ -68,10 +68,7 @@ describe("AuthGuard", () => { { path: "", component: EmptyComponent }, { path: "guarded-route", component: EmptyComponent, canActivate: [authGuard] }, { path: "lock", component: EmptyComponent }, - { path: "set-password", component: EmptyComponent }, - { path: "set-password-jit", component: EmptyComponent }, { path: "set-initial-password", component: EmptyComponent, canActivate: [authGuard] }, - { path: "update-temp-password", component: EmptyComponent, canActivate: [authGuard] }, { path: "change-password", component: EmptyComponent }, { path: "remove-password", component: EmptyComponent, canActivate: [authGuard] }, ]), @@ -125,109 +122,58 @@ describe("AuthGuard", () => { }); describe("given user is Locked", () => { - describe("given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => { - it("should redirect to /set-initial-password when the user has ForceSetPasswordReaason.TdeOffboardingUntrustedDevice", async () => { - const { router } = setup( - AuthenticationStatus.Locked, - ForceSetPasswordReason.TdeOffboardingUntrustedDevice, - false, - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); + it("should redirect to /set-initial-password when the user has ForceSetPasswordReaason.TdeOffboardingUntrustedDevice", async () => { + const { router } = setup( + AuthenticationStatus.Locked, + ForceSetPasswordReason.TdeOffboardingUntrustedDevice, + false, + ); - await router.navigate(["guarded-route"]); - expect(router.url).toBe("/set-initial-password"); - }); + await router.navigate(["guarded-route"]); + expect(router.url).toBe("/set-initial-password"); + }); - it("should allow navigation to continue to /set-initial-password when the user has ForceSetPasswordReason.TdeOffboardingUntrustedDevice", async () => { - const { router } = setup( - AuthenticationStatus.Unlocked, - ForceSetPasswordReason.TdeOffboardingUntrustedDevice, - false, - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); + it("should allow navigation to continue to /set-initial-password when the user has ForceSetPasswordReason.TdeOffboardingUntrustedDevice", async () => { + const { router } = setup( + AuthenticationStatus.Unlocked, + ForceSetPasswordReason.TdeOffboardingUntrustedDevice, + false, + ); - await router.navigate(["/set-initial-password"]); - expect(router.url).toContain("/set-initial-password"); - }); + await router.navigate(["/set-initial-password"]); + expect(router.url).toContain("/set-initial-password"); }); }); - describe("given user is Unlocked", () => { - describe("given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => { - const tests = [ - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, - ForceSetPasswordReason.TdeOffboarding, - ]; + describe("given user is Unlocked and ForceSetPasswordReason requires setting an initial password", () => { + const tests = [ + ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, + ForceSetPasswordReason.TdeOffboarding, + ]; - describe("given user attempts to navigate to an auth guarded route", () => { - tests.forEach((reason) => { - it(`should redirect to /set-initial-password when the user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup( - AuthenticationStatus.Unlocked, - reason, - false, - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); + describe("given user attempts to navigate to an auth guarded route", () => { + tests.forEach((reason) => { + it(`should redirect to /set-initial-password when the user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { + const { router } = setup(AuthenticationStatus.Unlocked, reason, false); - await router.navigate(["guarded-route"]); - expect(router.url).toContain("/set-initial-password"); - }); - }); - }); - - describe("given user attempts to navigate to /set-initial-password", () => { - tests.forEach((reason) => { - it(`should allow navigation to continue to /set-initial-password when the user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup( - AuthenticationStatus.Unlocked, - reason, - false, - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); - - await router.navigate(["/set-initial-password"]); - expect(router.url).toContain("/set-initial-password"); - }); + await router.navigate(["guarded-route"]); + expect(router.url).toContain("/set-initial-password"); }); }); }); - describe("given the PM16117_SetInitialPasswordRefactor feature flag is OFF", () => { - const tests = [ - { - reason: ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, - url: "/set-password", - }, - { - reason: ForceSetPasswordReason.TdeOffboarding, - url: "/update-temp-password", - }, - ]; + describe("given user attempts to navigate to /set-initial-password", () => { + tests.forEach((reason) => { + it(`should allow navigation to continue to /set-initial-password when the user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { + const { router } = setup(AuthenticationStatus.Unlocked, reason, false); - describe("given user attempts to navigate to an auth guarded route", () => { - tests.forEach(({ reason, url }) => { - it(`should redirect to ${url} when user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup(AuthenticationStatus.Unlocked, reason); - - await router.navigate(["/guarded-route"]); - expect(router.url).toContain(url); - }); - }); - }); - - describe("given user attempts to navigate to the set- or update- password route itself", () => { - tests.forEach(({ reason, url }) => { - it(`should allow navigation to continue to ${url} when user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup(AuthenticationStatus.Unlocked, reason); - - await router.navigate([url]); - expect(router.url).toContain(url); - }); + await router.navigate(["/set-initial-password"]); + expect(router.url).toContain("/set-initial-password"); }); }); }); - describe("given the PM16117_ChangeExistingPasswordRefactor feature flag is ON", () => { + describe("given user is Unlocked and ForceSetPasswordReason requires changing an existing password", () => { const tests = [ ForceSetPasswordReason.AdminForcePasswordReset, ForceSetPasswordReason.WeakMasterPassword, @@ -236,12 +182,7 @@ describe("AuthGuard", () => { describe("given user attempts to navigate to an auth guarded route", () => { tests.forEach((reason) => { it(`should redirect to /change-password when user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup( - AuthenticationStatus.Unlocked, - reason, - false, - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - ); + const { router } = setup(AuthenticationStatus.Unlocked, reason, false); await router.navigate(["guarded-route"]); expect(router.url).toContain("/change-password"); @@ -256,7 +197,6 @@ describe("AuthGuard", () => { AuthenticationStatus.Unlocked, ForceSetPasswordReason.AdminForcePasswordReset, false, - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, ); await router.navigate(["/change-password"]); @@ -265,34 +205,5 @@ describe("AuthGuard", () => { }); }); }); - - describe("given the PM16117_ChangeExistingPasswordRefactor feature flag is OFF", () => { - const tests = [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ]; - - describe("given user attempts to navigate to an auth guarded route", () => { - tests.forEach((reason) => { - it(`should redirect to /update-temp-password when user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup(AuthenticationStatus.Unlocked, reason); - - await router.navigate(["guarded-route"]); - expect(router.url).toContain("/update-temp-password"); - }); - }); - }); - - describe("given user attempts to navigate to /update-temp-password", () => { - tests.forEach((reason) => { - it(`should allow navigation to continue to /update-temp-password when user has ForceSetPasswordReason.${ForceSetPasswordReason[reason]}`, async () => { - const { router } = setup(AuthenticationStatus.Unlocked, reason); - - await router.navigate(["/update-temp-password"]); - expect(router.url).toContain("/update-temp-password"); - }); - }); - }); - }); }); }); diff --git a/libs/angular/src/auth/guards/auth.guard.ts b/libs/angular/src/auth/guards/auth.guard.ts index 3722a7c802a..8e8e70a6d29 100644 --- a/libs/angular/src/auth/guards/auth.guard.ts +++ b/libs/angular/src/auth/guards/auth.guard.ts @@ -14,10 +14,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; export const authGuard: CanActivateFn = async ( @@ -30,7 +28,6 @@ export const authGuard: CanActivateFn = async ( const keyConnectorService = inject(KeyConnectorService); const accountService = inject(AccountService); const masterPasswordService = inject(MasterPasswordServiceAbstraction); - const configService = inject(ConfigService); const authStatus = await authService.getAuthStatus(); @@ -44,16 +41,11 @@ export const authGuard: CanActivateFn = async ( masterPasswordService.forceSetPasswordReason$(userId), ); - const isSetInitialPasswordFlagOn = await configService.getFeatureFlag( - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); - // User JIT provisioned into a master-password-encryption org if ( authStatus === AuthenticationStatus.Locked && forceSetPasswordReason === ForceSetPasswordReason.SsoNewJitProvisionedUser && - !routerState.url.includes("set-initial-password") && - isSetInitialPasswordFlagOn + !routerState.url.includes("set-initial-password") ) { return router.createUrlTree(["/set-initial-password"]); } @@ -62,8 +54,7 @@ export const authGuard: CanActivateFn = async ( if ( authStatus === AuthenticationStatus.Locked && forceSetPasswordReason === ForceSetPasswordReason.TdeOffboardingUntrustedDevice && - !routerState.url.includes("set-initial-password") && - isSetInitialPasswordFlagOn + !routerState.url.includes("set-initial-password") ) { return router.createUrlTree(["/set-initial-password"]); } @@ -90,39 +81,28 @@ export const authGuard: CanActivateFn = async ( return router.createUrlTree(["/remove-password"]); } - // TDE org user has "manage account recovery" permission + // Handle cases where a user needs to set a password when they don't already have one: + // - TDE org user has been given "manage account recovery" permission + // - TDE offboarding on a trusted device, where we have access to their encryption key wrap with their new password if ( - forceSetPasswordReason === - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission && - !routerState.url.includes("set-password") && + (forceSetPasswordReason === + ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission || + forceSetPasswordReason === ForceSetPasswordReason.TdeOffboarding) && !routerState.url.includes("set-initial-password") ) { - const route = isSetInitialPasswordFlagOn ? "/set-initial-password" : "/set-password"; + const route = "/set-initial-password"; return router.createUrlTree([route]); } - // TDE Offboarding on trusted device - if ( - forceSetPasswordReason === ForceSetPasswordReason.TdeOffboarding && - !routerState.url.includes("update-temp-password") && - !routerState.url.includes("set-initial-password") - ) { - const route = isSetInitialPasswordFlagOn ? "/set-initial-password" : "/update-temp-password"; - return router.createUrlTree([route]); - } - - const isChangePasswordFlagOn = await configService.getFeatureFlag( - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - ); - - // Post- Account Recovery or Weak Password on login + // Handle cases where a user has a password but needs to set a new one: + // - Account recovery + // - Weak Password on login if ( (forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset || forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) && - !routerState.url.includes("update-temp-password") && !routerState.url.includes("change-password") ) { - const route = isChangePasswordFlagOn ? "/change-password" : "/update-temp-password"; + const route = "/change-password"; return router.createUrlTree([route]); } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 0ad7b57d9b3..aaabc405da0 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -22,13 +22,11 @@ import { DefaultLoginComponentService, DefaultLoginDecryptionOptionsService, DefaultRegistrationFinishService, - DefaultSetPasswordJitService, DefaultTwoFactorAuthComponentService, DefaultTwoFactorAuthWebAuthnComponentService, LoginComponentService, LoginDecryptionOptionsService, RegistrationFinishService as RegistrationFinishServiceAbstraction, - SetPasswordJitService, TwoFactorAuthComponentService, TwoFactorAuthWebAuthnComponentService, } from "@bitwarden/auth/angular"; @@ -1417,21 +1415,6 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultOrganizationInviteService, deps: [], }), - safeProvider({ - provide: SetPasswordJitService, - useClass: DefaultSetPasswordJitService, - deps: [ - EncryptService, - I18nServiceAbstraction, - KdfConfigService, - KeyService, - MasterPasswordApiServiceAbstraction, - InternalMasterPasswordServiceAbstraction, - OrganizationApiServiceAbstraction, - OrganizationUserApiService, - InternalUserDecryptionOptionsServiceAbstraction, - ], - }), safeProvider({ provide: SetInitialPasswordService, useClass: DefaultSetInitialPasswordService, diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index aa0041c7ec3..3e8dc575c7b 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -41,11 +41,6 @@ export * from "./registration/registration-env-selector/registration-env-selecto export * from "./registration/registration-finish/registration-finish.service"; export * from "./registration/registration-finish/default-registration-finish.service"; -// set password (JIT user) -export * from "./set-password-jit/set-password-jit.component"; -export * from "./set-password-jit/set-password-jit.service.abstraction"; -export * from "./set-password-jit/default-set-password-jit.service"; - // user verification export * from "./user-verification/user-verification-dialog.component"; export * from "./user-verification/user-verification-dialog.types"; diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index b3509850ac0..2a2be148a86 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -18,7 +18,6 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; @@ -230,29 +229,21 @@ export class LoginComponent implements OnInit, OnDestroy { return; } - let credentials: PasswordLoginCredentials; + // Try to retrieve any org policies from an org invite now so we can send it to the + // login strategies. Since it is optional and we only want to be doing this on the + // web we will only send in content in the right context. + const orgPoliciesFromInvite = this.loginComponentService.getOrgPoliciesFromOrgInvite + ? await this.loginComponentService.getOrgPoliciesFromOrgInvite() + : null; - if ( - await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) - ) { - // Try to retrieve any org policies from an org invite now so we can send it to the - // login strategies. Since it is optional and we only want to be doing this on the - // web we will only send in content in the right context. - const orgPoliciesFromInvite = this.loginComponentService.getOrgPoliciesFromOrgInvite - ? await this.loginComponentService.getOrgPoliciesFromOrgInvite() - : null; + const orgMasterPasswordPolicyOptions = orgPoliciesFromInvite?.enforcedPasswordPolicyOptions; - const orgMasterPasswordPolicyOptions = orgPoliciesFromInvite?.enforcedPasswordPolicyOptions; - - credentials = new PasswordLoginCredentials( - email, - masterPassword, - undefined, - orgMasterPasswordPolicyOptions, - ); - } else { - credentials = new PasswordLoginCredentials(email, masterPassword); - } + const credentials = new PasswordLoginCredentials( + email, + masterPassword, + undefined, + orgMasterPasswordPolicyOptions, + ); try { const authResult = await this.loginStrategyService.logIn(credentials); @@ -332,7 +323,7 @@ export class LoginComponent implements OnInit, OnDestroy { await this.loginSuccessHandlerService.run(authResult.userId); // Determine where to send the user next - // The AuthGuard will handle routing to update-temp-password based on state + // The AuthGuard will handle routing to change-password based on state // TODO: PM-18269 - evaluate if we can combine this with the // password evaluation done in the password login strategy. @@ -344,7 +335,7 @@ export class LoginComponent implements OnInit, OnDestroy { if (orgPolicies) { // Since we have retrieved the policies, we can go ahead and set them into state for future use - // e.g., the update-password page currently only references state for policy data and + // e.g., the change-password page currently only references state for policy data and // doesn't fallback to pulling them from the server like it should if they are null. await this.setPoliciesIntoState(authResult.userId, orgPolicies.policies); @@ -352,13 +343,7 @@ export class LoginComponent implements OnInit, OnDestroy { orgPolicies.enforcedPasswordPolicyOptions, ); if (isPasswordChangeRequired) { - const changePasswordFeatureFlagOn = await this.configService.getFeatureFlag( - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - ); - - await this.router.navigate( - changePasswordFeatureFlagOn ? ["change-password"] : ["update-password"], - ); + await this.router.navigate(["change-password"]); return; } } diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts index 4325b4bcbc1..6362b901fc8 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -10,7 +10,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -151,25 +150,17 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy { this.loginSuccessHandlerService.run(authResult.userId); // TODO: PM-22663 use the new service to handle routing. + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const forceSetPasswordReason = await firstValueFrom( + this.masterPasswordService.forceSetPasswordReason$(activeUserId), + ); + if ( - await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) + forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword || + forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(getUserId), - ); - - const forceSetPasswordReason = await firstValueFrom( - this.masterPasswordService.forceSetPasswordReason$(activeUserId), - ); - - if ( - forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword || - forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset - ) { - await this.router.navigate(["/change-password"]); - } else { - await this.router.navigate(["/vault"]); - } + await this.router.navigate(["/change-password"]); } else { await this.router.navigate(["/vault"]); } diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts deleted file mode 100644 index 6a9a37700cc..00000000000 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { MockProxy, mock } from "jest-mock-extended"; -import { BehaviorSubject, of } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { - FakeUserDecryptionOptions as UserDecryptionOptions, - InternalUserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; -import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; -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 { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { PasswordInputResult } from "../input-password/password-input-result"; - -import { DefaultSetPasswordJitService } from "./default-set-password-jit.service"; -import { SetPasswordCredentials } from "./set-password-jit.service.abstraction"; - -describe("DefaultSetPasswordJitService", () => { - let sut: DefaultSetPasswordJitService; - - let masterPasswordApiService: MockProxy; - let keyService: MockProxy; - let encryptService: MockProxy; - let i18nService: MockProxy; - let kdfConfigService: MockProxy; - let masterPasswordService: MockProxy; - let organizationApiService: MockProxy; - let organizationUserApiService: MockProxy; - let userDecryptionOptionsService: MockProxy; - - beforeEach(() => { - masterPasswordApiService = mock(); - keyService = mock(); - encryptService = mock(); - i18nService = mock(); - kdfConfigService = mock(); - masterPasswordService = mock(); - organizationApiService = mock(); - organizationUserApiService = mock(); - userDecryptionOptionsService = mock(); - - sut = new DefaultSetPasswordJitService( - encryptService, - i18nService, - kdfConfigService, - keyService, - masterPasswordApiService, - masterPasswordService, - organizationApiService, - organizationUserApiService, - userDecryptionOptionsService, - ); - }); - - it("should instantiate the DefaultSetPasswordJitService", () => { - expect(sut).not.toBeFalsy(); - }); - - describe("setPassword", () => { - let masterKey: MasterKey; - let userKey: UserKey; - let userKeyEncString: EncString; - let protectedUserKey: [UserKey, EncString]; - let keyPair: [string, EncString]; - let keysRequest: KeysRequest; - let organizationKeys: OrganizationKeysResponse; - let orgPublicKey: Uint8Array; - - let orgSsoIdentifier: string; - let orgId: string; - let resetPasswordAutoEnroll: boolean; - let userId: UserId; - let passwordInputResult: PasswordInputResult; - let credentials: SetPasswordCredentials; - - let userDecryptionOptionsSubject: BehaviorSubject; - let setPasswordRequest: SetPasswordRequest; - - beforeEach(() => { - masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; - userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; - userKeyEncString = new EncString("userKeyEncrypted"); - protectedUserKey = [userKey, userKeyEncString]; - keyPair = ["publicKey", new EncString("privateKey")]; - keysRequest = new KeysRequest(keyPair[0], keyPair[1].encryptedString); - organizationKeys = { - privateKey: "orgPrivateKey", - publicKey: "orgPublicKey", - } as OrganizationKeysResponse; - orgPublicKey = Utils.fromB64ToArray(organizationKeys.publicKey); - - orgSsoIdentifier = "orgSsoIdentifier"; - orgId = "orgId"; - resetPasswordAutoEnroll = false; - userId = "userId" as UserId; - - passwordInputResult = { - newMasterKey: masterKey, - newServerMasterKeyHash: "newServerMasterKeyHash", - newLocalMasterKeyHash: "newLocalMasterKeyHash", - newPasswordHint: "newPasswordHint", - kdfConfig: DEFAULT_KDF_CONFIG, - newPassword: "newPassword", - }; - - credentials = { - newMasterKey: passwordInputResult.newMasterKey, - newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, - newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash, - newPasswordHint: passwordInputResult.newPasswordHint, - kdfConfig: passwordInputResult.kdfConfig, - orgSsoIdentifier, - orgId, - resetPasswordAutoEnroll, - userId, - }; - - userDecryptionOptionsSubject = new BehaviorSubject(null); - userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject; - - setPasswordRequest = new SetPasswordRequest( - passwordInputResult.newServerMasterKeyHash, - protectedUserKey[1].encryptedString, - passwordInputResult.newPasswordHint, - orgSsoIdentifier, - keysRequest, - passwordInputResult.kdfConfig.kdfType, - passwordInputResult.kdfConfig.iterations, - ); - }); - - function setupSetPasswordMocks(hasUserKey = true) { - if (!hasUserKey) { - keyService.userKey$.mockReturnValue(of(null)); - keyService.makeUserKey.mockResolvedValue(protectedUserKey); - } else { - keyService.userKey$.mockReturnValue(of(userKey)); - keyService.encryptUserKeyWithMasterKey.mockResolvedValue(protectedUserKey); - } - - keyService.makeKeyPair.mockResolvedValue(keyPair); - - masterPasswordApiService.setPassword.mockResolvedValue(undefined); - masterPasswordService.setForceSetPasswordReason.mockResolvedValue(undefined); - - userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true })); - userDecryptionOptionsService.setUserDecryptionOptions.mockResolvedValue(undefined); - kdfConfigService.setKdfConfig.mockResolvedValue(undefined); - keyService.setUserKey.mockResolvedValue(undefined); - - keyService.setPrivateKey.mockResolvedValue(undefined); - - masterPasswordService.setMasterKeyHash.mockResolvedValue(undefined); - } - - function setupResetPasswordAutoEnrollMocks(organizationKeysExist = true) { - if (organizationKeysExist) { - organizationApiService.getKeys.mockResolvedValue(organizationKeys); - } else { - organizationApiService.getKeys.mockResolvedValue(null); - return; - } - - keyService.userKey$.mockReturnValue(of(userKey)); - encryptService.encapsulateKeyUnsigned.mockResolvedValue(userKeyEncString); - - organizationUserApiService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue( - undefined, - ); - } - - it("should set password successfully (given a user key)", async () => { - // Arrange - setupSetPasswordMocks(); - - // Act - await sut.setPassword(credentials); - - // Assert - expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); - }); - - it("should set password successfully (given no user key)", async () => { - // Arrange - setupSetPasswordMocks(false); - - // Act - await sut.setPassword(credentials); - - // Assert - expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); - }); - - it("should handle reset password auto enroll", async () => { - // Arrange - credentials.resetPasswordAutoEnroll = true; - - setupSetPasswordMocks(); - setupResetPasswordAutoEnrollMocks(); - - // Act - await sut.setPassword(credentials); - - // Assert - expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); - expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId); - expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(userKey, orgPublicKey); - expect( - organizationUserApiService.putOrganizationUserResetPasswordEnrollment, - ).toHaveBeenCalled(); - }); - - it("when handling reset password auto enroll, it should throw an error if organization keys are not found", async () => { - // Arrange - credentials.resetPasswordAutoEnroll = true; - - setupSetPasswordMocks(); - setupResetPasswordAutoEnrollMocks(false); - - // Act and Assert - await expect(sut.setPassword(credentials)).rejects.toThrow(); - expect( - organizationUserApiService.putOrganizationUserResetPasswordEnrollment, - ).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts deleted file mode 100644 index 5fc3272b650..00000000000 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ /dev/null @@ -1,176 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { - OrganizationUserApiService, - OrganizationUserResetPasswordEnrollmentRequest, -} from "@bitwarden/admin-console/common"; -import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; -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 { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management"; - -import { - SetPasswordCredentials, - SetPasswordJitService, -} from "./set-password-jit.service.abstraction"; - -export class DefaultSetPasswordJitService implements SetPasswordJitService { - constructor( - protected encryptService: EncryptService, - protected i18nService: I18nService, - protected kdfConfigService: KdfConfigService, - protected keyService: KeyService, - protected masterPasswordApiService: MasterPasswordApiService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected organizationApiService: OrganizationApiServiceAbstraction, - protected organizationUserApiService: OrganizationUserApiService, - protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - ) {} - - async setPassword(credentials: SetPasswordCredentials): Promise { - const { - newMasterKey, - newServerMasterKeyHash, - newLocalMasterKeyHash, - newPasswordHint, - kdfConfig, - orgSsoIdentifier, - orgId, - resetPasswordAutoEnroll, - userId, - } = credentials; - - for (const [key, value] of Object.entries(credentials)) { - if (value == null) { - throw new Error(`${key} not found. Could not set password.`); - } - } - - const protectedUserKey = await this.makeProtectedUserKey(newMasterKey, userId); - if (protectedUserKey == null) { - throw new Error("protectedUserKey not found. Could not set password."); - } - - // Since this is an existing JIT provisioned user in a MP encryption org setting first password, - // they will not already have a user asymmetric key pair so we must create it for them. - const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey); - - const request = new SetPasswordRequest( - newServerMasterKeyHash, - protectedUserKey[1].encryptedString, - newPasswordHint, - orgSsoIdentifier, - keysRequest, - kdfConfig.kdfType, - kdfConfig.iterations, - ); - - await this.masterPasswordApiService.setPassword(request); - - // Clear force set password reason to allow navigation back to vault. - await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); - - // User now has a password so update account decryption options in state - await this.updateAccountDecryptionProperties(newMasterKey, kdfConfig, protectedUserKey, userId); - - await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId); - - await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); - - if (resetPasswordAutoEnroll) { - await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId); - } - } - - private async makeProtectedUserKey( - masterKey: MasterKey, - userId: UserId, - ): Promise<[UserKey, EncString]> { - let protectedUserKey: [UserKey, EncString] = null; - - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); - - if (userKey == null) { - protectedUserKey = await this.keyService.makeUserKey(masterKey); - } else { - protectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey); - } - - return protectedUserKey; - } - - private async makeKeyPairAndRequest( - protectedUserKey: [UserKey, EncString], - ): Promise<[[string, EncString], KeysRequest]> { - const keyPair = await this.keyService.makeKeyPair(protectedUserKey[0]); - if (keyPair == null) { - throw new Error("keyPair not found. Could not set password."); - } - const keysRequest = new KeysRequest(keyPair[0], keyPair[1].encryptedString); - - return [keyPair, keysRequest]; - } - - private async updateAccountDecryptionProperties( - masterKey: MasterKey, - kdfConfig: KdfConfig, - protectedUserKey: [UserKey, EncString], - userId: UserId, - ) { - const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - userDecryptionOpts.hasMasterPassword = true; - await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); - await this.kdfConfigService.setKdfConfig(userId, kdfConfig); - await this.masterPasswordService.setMasterKey(masterKey, userId); - await this.keyService.setUserKey(protectedUserKey[0], userId); - } - - private async handleResetPasswordAutoEnroll( - masterKeyHash: string, - orgId: string, - userId: UserId, - ) { - const organizationKeys = await this.organizationApiService.getKeys(orgId); - - if (organizationKeys == null) { - throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } - - const publicKey = Utils.fromB64ToArray(organizationKeys.publicKey); - - // RSA Encrypt user key with organization public key - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); - - if (userKey == null) { - throw new Error("userKey not found. Could not handle reset password auto enroll."); - } - - const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey); - - const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); - resetRequest.masterPasswordHash = masterKeyHash; - resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; - - await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( - orgId, - userId, - resetRequest, - ); - } -} diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html b/libs/auth/src/angular/set-password-jit/set-password-jit.component.html deleted file mode 100644 index 797f18732cb..00000000000 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html +++ /dev/null @@ -1,24 +0,0 @@ - - - {{ "loading" | i18n }} - - - - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - - diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts deleted file mode 100644 index 1a2674cd3d4..00000000000 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ /dev/null @@ -1,135 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; - -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ToastService } from "../../../../components/src/toast"; -import { - InputPasswordComponent, - InputPasswordFlow, -} from "../input-password/input-password.component"; -import { PasswordInputResult } from "../input-password/password-input-result"; - -import { - SetPasswordCredentials, - SetPasswordJitService, -} from "./set-password-jit.service.abstraction"; - -@Component({ - selector: "auth-set-password-jit", - templateUrl: "set-password-jit.component.html", - imports: [CommonModule, InputPasswordComponent, JslibModule], -}) -export class SetPasswordJitComponent implements OnInit { - protected inputPasswordFlow = InputPasswordFlow.SetInitialPasswordAuthedUser; - protected email: string; - protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions; - protected orgId: string; - protected orgSsoIdentifier: string; - protected resetPasswordAutoEnroll: boolean; - protected submitting = false; - protected syncLoading = true; - protected userId: UserId; - - constructor( - private accountService: AccountService, - private activatedRoute: ActivatedRoute, - private i18nService: I18nService, - private organizationApiService: OrganizationApiServiceAbstraction, - private policyApiService: PolicyApiServiceAbstraction, - private router: Router, - private setPasswordJitService: SetPasswordJitService, - private syncService: SyncService, - private toastService: ToastService, - private validationService: ValidationService, - ) {} - - async ngOnInit() { - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - this.userId = activeAccount?.id; - this.email = activeAccount?.email; - - await this.syncService.fullSync(true); - this.syncLoading = false; - - await this.handleQueryParams(); - } - - private async handleQueryParams() { - const qParams = await firstValueFrom(this.activatedRoute.queryParams); - - if (qParams.identifier != null) { - try { - this.orgSsoIdentifier = qParams.identifier; - - const autoEnrollStatus = await this.organizationApiService.getAutoEnrollStatus( - this.orgSsoIdentifier, - ); - this.orgId = autoEnrollStatus.id; - this.resetPasswordAutoEnroll = autoEnrollStatus.resetPasswordEnabled; - this.masterPasswordPolicyOptions = - await this.policyApiService.getMasterPasswordPolicyOptsForOrgUser(autoEnrollStatus.id); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - } - - protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { - this.submitting = true; - - const credentials: SetPasswordCredentials = { - newMasterKey: passwordInputResult.newMasterKey, - newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, - newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash, - newPasswordHint: passwordInputResult.newPasswordHint, - kdfConfig: passwordInputResult.kdfConfig, - orgSsoIdentifier: this.orgSsoIdentifier, - orgId: this.orgId, - resetPasswordAutoEnroll: this.resetPasswordAutoEnroll, - userId: this.userId, - }; - - try { - await this.setPasswordJitService.setPassword(credentials); - } catch (e) { - this.validationService.showError(e); - this.submitting = false; - return; - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("accountSuccessfullyCreated"), - }); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("inviteAccepted"), - }); - - this.submitting = false; - - await this.router.navigate(["vault"]); - } -} diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts deleted file mode 100644 index 92db88868a2..00000000000 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.service.abstraction.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey } from "@bitwarden/common/types/key"; -import { KdfConfig } from "@bitwarden/key-management"; - -export interface SetPasswordCredentials { - newMasterKey: MasterKey; - newServerMasterKeyHash: string; - newLocalMasterKeyHash: string; - newPasswordHint: string; - kdfConfig: KdfConfig; - orgSsoIdentifier: string; - orgId: string; - resetPasswordAutoEnroll: boolean; - userId: UserId; -} - -/** - * This service handles setting a password for a "just-in-time" provisioned user. - * - * A "just-in-time" (JIT) provisioned user is a user who does not have a registered account at the - * time they first click "Login with SSO". Once they click "Login with SSO" we register the account on - * the fly ("just-in-time"). - */ -export abstract class SetPasswordJitService { - /** - * Sets the password for a JIT provisioned user. - * - * @param credentials An object of the credentials needed to set the password for a JIT provisioned user - * @throws If any property on the `credentials` object is null or undefined, or if a protectedUserKey - * or newKeyPair could not be created. - */ - abstract setPassword(credentials: SetPasswordCredentials): Promise; -} diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index 07b59ac661f..8acd6865b70 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -23,12 +23,10 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -118,7 +116,6 @@ export class SsoComponent implements OnInit { private toastService: ToastService, private ssoComponentService: SsoComponentService, private loginSuccessHandlerService: LoginSuccessHandlerService, - private configService: ConfigService, ) { environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html"; @@ -534,11 +531,7 @@ export class SsoComponent implements OnInit { } private async handleChangePasswordRequired(orgIdentifier: string) { - const isSetInitialPasswordRefactorFlagOn = await this.configService.getFeatureFlag( - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); - const route = isSetInitialPasswordRefactorFlagOn ? "set-initial-password" : "set-password-jit"; - + const route = "set-initial-password"; await this.router.navigate([route], { queryParams: { identifier: orgIdentifier, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index e7678102360..62271feee59 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -2,7 +2,7 @@ import { Component } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ActivatedRoute, Router, convertToParamMap } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { @@ -24,8 +24,10 @@ import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication- import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { + InternalMasterPasswordServiceAbstraction, + MasterPasswordServiceAbstraction, +} from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -66,7 +68,7 @@ describe("TwoFactorAuthComponent", () => { let mockLoginEmailService: MockProxy; let mockUserDecryptionOptionsService: MockProxy; let mockSsoLoginService: MockProxy; - let mockMasterPasswordService: FakeMasterPasswordService; + let mockMasterPasswordService: MockProxy; let mockAccountService: FakeAccountService; let mockDialogService: MockProxy; let mockToastService: MockProxy; @@ -107,7 +109,7 @@ describe("TwoFactorAuthComponent", () => { mockUserDecryptionOptionsService = mock(); mockSsoLoginService = mock(); mockAccountService = mockAccountServiceWith(userId); - mockMasterPasswordService = new FakeMasterPasswordService(); + mockMasterPasswordService = mock(); mockDialogService = mock(); mockToastService = mock(); mockTwoFactorAuthCompService = mock(); @@ -212,6 +214,7 @@ describe("TwoFactorAuthComponent", () => { }, { provide: AuthService, useValue: mockAuthService }, { provide: ConfigService, useValue: mockConfigService }, + { provide: MasterPasswordServiceAbstraction, useValue: mockMasterPasswordService }, ], }); @@ -267,54 +270,16 @@ describe("TwoFactorAuthComponent", () => { selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword); }); - describe("Given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => { - it("navigates to the /set-initial-password route when user doesn't have a MP and key connector isn't enabled", async () => { - // Arrange - mockConfigService.getFeatureFlag.mockResolvedValue(true); - - // Act - await component.submit("testToken"); - - // Assert - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith(["set-initial-password"], { - queryParams: { - identifier: component.orgSsoIdentifier, - }, - }); - }); - }); - - describe("Given the PM16117_SetInitialPasswordRefactor feature flag is OFF", () => { - it("navigates to the /set-password route when user doesn't have a MP and key connector isn't enabled", async () => { - // Arrange - mockConfigService.getFeatureFlag.mockResolvedValue(false); - - // Act - await component.submit("testToken"); - - // Assert - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith(["set-password"], { - queryParams: { - identifier: component.orgSsoIdentifier, - }, - }); - }); - }); - }); - - describe("Given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => { - it("does not navigate to the /set-initial-password route when the user has key connector even if user has no master password", async () => { + it("navigates to the /set-initial-password route when user doesn't have a MP and key connector isn't enabled", async () => { + // Arrange mockConfigService.getFeatureFlag.mockResolvedValue(true); - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.noMasterPasswordWithKeyConnector, - ); + // Act + await component.submit("testToken"); - await component.submit(token, remember); - - expect(mockRouter.navigate).not.toHaveBeenCalledWith(["set-initial-password"], { + // Assert + expect(mockRouter.navigate).toHaveBeenCalledTimes(1); + expect(mockRouter.navigate).toHaveBeenCalledWith(["set-initial-password"], { queryParams: { identifier: component.orgSsoIdentifier, }, @@ -322,21 +287,19 @@ describe("TwoFactorAuthComponent", () => { }); }); - describe("Given the PM16117_SetInitialPasswordRefactor feature flag is OFF", () => { - it("does not navigate to the /set-password route when the user has key connector even if user has no master password", async () => { - mockConfigService.getFeatureFlag.mockResolvedValue(false); + it("does not navigate to the /set-initial-password route when the user has key connector even if user has no master password", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.noMasterPasswordWithKeyConnector, - ); + selectedUserDecryptionOptions.next( + mockUserDecryptionOpts.noMasterPasswordWithKeyConnector, + ); - await component.submit(token, remember); + await component.submit(token, remember); - expect(mockRouter.navigate).not.toHaveBeenCalledWith(["set-password"], { - queryParams: { - identifier: component.orgSsoIdentifier, - }, - }); + expect(mockRouter.navigate).not.toHaveBeenCalledWith(["set-initial-password"], { + queryParams: { + identifier: component.orgSsoIdentifier, + }, }); }); }); @@ -344,6 +307,9 @@ describe("TwoFactorAuthComponent", () => { it("navigates to the component's defined success route (vault is default) when the login is successful", async () => { mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); mockAuthService.activeAccountStatus$ = new BehaviorSubject(AuthenticationStatus.Unlocked); + mockMasterPasswordService.forceSetPasswordReason$.mockReturnValue( + of(ForceSetPasswordReason.None), + ); // Act await component.submit("testToken"); @@ -409,7 +375,7 @@ describe("TwoFactorAuthComponent", () => { await component.submit(token, remember); // Assert - expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( + expect(mockMasterPasswordService.setForceSetPasswordReason).toHaveBeenCalledWith( ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, userId, ); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 50cc2d88d6a..07746cf6479 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -17,7 +17,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, UserDecryptionOptionsServiceAbstraction, TrustedDeviceUserDecryptionOption, UserDecryptionOptions, @@ -32,9 +31,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -156,7 +153,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private activatedRoute: ActivatedRoute, private logService: LogService, private twoFactorService: TwoFactorService, - private loginEmailService: LoginEmailServiceAbstraction, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private ssoLoginService: SsoLoginServiceAbstraction, private masterPasswordService: InternalMasterPasswordServiceAbstraction, @@ -171,7 +167,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private loginSuccessHandlerService: LoginSuccessHandlerService, private twoFactorAuthComponentCacheService: TwoFactorAuthComponentCacheService, private authService: AuthService, - private configService: ConfigService, ) {} async ngOnInit() { @@ -507,19 +502,15 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } // TODO: PM-22663 use the new service to handle routing. - if ( - await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) - ) { - const forceSetPasswordReason = await firstValueFrom( - this.masterPasswordService.forceSetPasswordReason$(userId), - ); + const forceSetPasswordReason = await firstValueFrom( + this.masterPasswordService.forceSetPasswordReason$(userId), + ); - if ( - forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword || - forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset - ) { - return "change-password"; - } + if ( + forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword || + forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset + ) { + return "change-password"; } return "vault"; @@ -575,11 +566,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } private async handleChangePasswordRequired(orgIdentifier: string | undefined) { - const isSetInitialPasswordRefactorFlagOn = await this.configService.getFeatureFlag( - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); - const route = isSetInitialPasswordRefactorFlagOn ? "set-initial-password" : "set-password"; - + const route = "set-initial-password"; await this.router.navigate([route], { queryParams: { identifier: orgIdentifier, diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 61a06f94b02..8e3867d1b36 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -12,7 +12,6 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; import { @@ -221,7 +220,10 @@ describe("PasswordLoginStrategy", () => { await passwordLoginStrategy.logIn(credentials); - expect(policyService.evaluateMasterPassword).not.toHaveBeenCalled(); + expect(masterPasswordService.mock.setForceSetPasswordReason).not.toHaveBeenCalledWith( + ForceSetPasswordReason.WeakMasterPassword, + userId, + ); }); it("does not force the user to update their master password when it meets requirements", async () => { @@ -230,7 +232,10 @@ describe("PasswordLoginStrategy", () => { await passwordLoginStrategy.logIn(credentials); - expect(policyService.evaluateMasterPassword).toHaveBeenCalled(); + expect(masterPasswordService.mock.setForceSetPasswordReason).not.toHaveBeenCalledWith( + ForceSetPasswordReason.WeakMasterPassword, + userId, + ); }); it("when given master password policies as part of the login credentials from an org invite, it combines them with the token response policies to evaluate the user's password as weak", async () => { @@ -242,12 +247,6 @@ describe("PasswordLoginStrategy", () => { policyService.evaluateMasterPassword.mockReturnValue(false); tokenService.decodeAccessToken.mockResolvedValue({ sub: userId }); - jest - .spyOn(configService, "getFeatureFlag") - .mockImplementation((flag: FeatureFlag) => - Promise.resolve(flag === FeatureFlag.PM16117_ChangeExistingPasswordRefactor), - ); - credentials.masterPasswordPoliciesFromOrgInvite = Object.assign( new MasterPasswordPolicyOptions(), { @@ -296,9 +295,16 @@ describe("PasswordLoginStrategy", () => { it("forces the user to update their master password on successful login when it does not meet master password policy requirements", async () => { passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 0 } as any); - policyService.evaluateMasterPassword.mockReturnValue(false); tokenService.decodeAccessToken.mockResolvedValue({ sub: userId }); + const combinedMasterPasswordPolicyOptions = Object.assign(new MasterPasswordPolicyOptions(), { + enforceOnLogin: true, + }); + policyService.combineMasterPasswordPolicyOptions.mockReturnValue( + combinedMasterPasswordPolicyOptions, + ); + policyService.evaluateMasterPassword.mockReturnValue(false); + await passwordLoginStrategy.logIn(credentials); expect(policyService.evaluateMasterPassword).toHaveBeenCalled(); @@ -330,9 +336,16 @@ describe("PasswordLoginStrategy", () => { it("forces the user to update their master password on successful 2FA login when it does not meet master password policy requirements", async () => { passwordStrengthService.getPasswordStrength.mockReturnValue({ score: 0 } as any); - policyService.evaluateMasterPassword.mockReturnValue(false); tokenService.decodeAccessToken.mockResolvedValue({ sub: userId }); + const combinedMasterPasswordPolicyOptions = Object.assign(new MasterPasswordPolicyOptions(), { + enforceOnLogin: true, + }); + policyService.combineMasterPasswordPolicyOptions.mockReturnValue( + combinedMasterPasswordPolicyOptions, + ); + policyService.evaluateMasterPassword.mockReturnValue(false); + const token2FAResponse = new IdentityTwoFactorResponse({ TwoFactorProviders: ["0"], TwoFactorProviders2: { 0: null }, diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index cd3d5df1d5e..3482e73d5d7 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -12,7 +12,6 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -171,35 +170,22 @@ export class PasswordLoginStrategy extends LoginStrategy { return; } - // The identity result can contain master password policies for the user's organizations - let masterPasswordPolicyOptions: MasterPasswordPolicyOptions | undefined; + // The identity result can contain master password policies for the user's organizations. + // Get the master password policy options from both the org invite and the identity response. + const masterPasswordPolicyOptions = this.policyService.combineMasterPasswordPolicyOptions( + credentials.masterPasswordPoliciesFromOrgInvite, + this.getMasterPasswordPolicyOptionsFromResponse(identityResponse), + ); + // We deliberately do not check enforceOnLogin as existing users who are logging + // in after getting an org invite should always be forced to set a password that + // meets the org's policy. Org Invite -> Registration also works this way for + // new BW users as well. if ( - await this.configService.getFeatureFlag(FeatureFlag.PM16117_ChangeExistingPasswordRefactor) + !credentials.masterPasswordPoliciesFromOrgInvite && + !masterPasswordPolicyOptions?.enforceOnLogin ) { - // Get the master password policy options from both the org invite and the identity response. - masterPasswordPolicyOptions = this.policyService.combineMasterPasswordPolicyOptions( - credentials.masterPasswordPoliciesFromOrgInvite, - this.getMasterPasswordPolicyOptionsFromResponse(identityResponse), - ); - - // We deliberately do not check enforceOnLogin as existing users who are logging - // in after getting an org invite should always be forced to set a password that - // meets the org's policy. Org Invite -> Registration also works this way for - // new BW users as well. - if ( - !credentials.masterPasswordPoliciesFromOrgInvite && - !masterPasswordPolicyOptions?.enforceOnLogin - ) { - return; - } - } else { - masterPasswordPolicyOptions = - this.getMasterPasswordPolicyOptionsFromResponse(identityResponse); - - if (!masterPasswordPolicyOptions?.enforceOnLogin) { - return; - } + return; } // If there is a policy active, evaluate the supplied password before its no longer in memory diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 47a9d19f651..f057dc47c63 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -10,7 +10,6 @@ import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; @@ -83,6 +82,7 @@ describe("SsoLoginStrategy", () => { const ssoCodeVerifier = "SSO_CODE_VERIFIER"; const ssoRedirectUrl = "SSO_REDIRECT_URL"; const ssoOrgId = "SSO_ORG_ID"; + const privateKey = "userKeyEncryptedPrivateKey"; beforeEach(async () => { accountService = mockAccountServiceWith(userId); @@ -114,6 +114,9 @@ describe("SsoLoginStrategy", () => { tokenService.decodeAccessToken.mockResolvedValue({ sub: userId, }); + keyService.userEncryptedPrivateKey$ + .calledWith(userId) + .mockReturnValue(of(privateKey as EncryptedString)); const mockVaultTimeoutAction = VaultTimeoutAction.Lock; const mockVaultTimeoutActionBSub = new BehaviorSubject( @@ -163,6 +166,7 @@ describe("SsoLoginStrategy", () => { it("sends SSO information to server", async () => { apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); + keyService.hasUserKey.mockResolvedValue(true); await ssoLoginStrategy.logIn(credentials); @@ -185,6 +189,7 @@ describe("SsoLoginStrategy", () => { it("does not set keys for new SSO user flow", async () => { const tokenResponse = identityTokenResponseFactory(); tokenResponse.key = null; + tokenResponse.privateKey = null; apiService.postIdentityToken.mockResolvedValue(tokenResponse); await ssoLoginStrategy.logIn(credentials); @@ -210,42 +215,28 @@ describe("SsoLoginStrategy", () => { ); }); - describe("given the PM16117_SetInitialPasswordRefactor feature flag is ON", () => { - beforeEach(() => { - configService.getFeatureFlag.mockImplementation(async (flag) => { - if (flag === FeatureFlag.PM16117_SetInitialPasswordRefactor) { - return true; - } - return false; - }); - }); + describe("given the user does not have the `trustedDeviceOption`, does not have a master password, is not using key connector, does not have a user key, but they DO have a `userKeyEncryptedPrivateKey`", () => { + it("should set the forceSetPasswordReason to TdeOffboardingUntrustedDevice", async () => { + // Arrange + const mockUserDecryptionOptions: IUserDecryptionOptionsServerResponse = { + HasMasterPassword: false, + TrustedDeviceOption: null, + KeyConnectorOption: null, + }; + const tokenResponse = identityTokenResponseFactory(null, mockUserDecryptionOptions); + apiService.postIdentityToken.mockResolvedValue(tokenResponse); - describe("given the user does not have the `trustedDeviceOption`, does not have a master password, is not using key connector, does not have a user key, but they DO have a `userKeyEncryptedPrivateKey`", () => { - it("should set the forceSetPasswordReason to TdeOffboardingUntrustedDevice", async () => { - // Arrange - const mockUserDecryptionOptions: IUserDecryptionOptionsServerResponse = { - HasMasterPassword: false, - TrustedDeviceOption: null, - KeyConnectorOption: null, - }; - const tokenResponse = identityTokenResponseFactory(null, mockUserDecryptionOptions); - apiService.postIdentityToken.mockResolvedValue(tokenResponse); + keyService.hasUserKey.mockResolvedValue(false); - keyService.userEncryptedPrivateKey$.mockReturnValue( - of("userKeyEncryptedPrivateKey" as EncryptedString), - ); - keyService.hasUserKey.mockResolvedValue(false); + // Act + await ssoLoginStrategy.logIn(credentials); - // Act - await ssoLoginStrategy.logIn(credentials); - - // Assert - expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledTimes(1); - expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( - ForceSetPasswordReason.TdeOffboardingUntrustedDevice, - userId, - ); - }); + // Assert + expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledTimes(1); + expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( + ForceSetPasswordReason.TdeOffboardingUntrustedDevice, + userId, + ); }); }); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index 8ab84f0968a..6f1231b3559 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -9,7 +9,6 @@ import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity- import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -344,38 +343,18 @@ export class SsoLoginStrategy extends LoginStrategy { tokenResponse: IdentityTokenResponse, userId: UserId, ): Promise { - const isSetInitialPasswordFlagOn = await this.configService.getFeatureFlag( - FeatureFlag.PM16117_SetInitialPasswordRefactor, - ); - - if (isSetInitialPasswordFlagOn) { - if (tokenResponse.hasMasterKeyEncryptedUserKey()) { - // User has masterKeyEncryptedUserKey, so set the userKeyEncryptedPrivateKey - // Note: new JIT provisioned SSO users will not yet have a user asymmetric key pair - // and so we don't want them falling into the createKeyPairForOldAccount flow - await this.keyService.setPrivateKey( - tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount(userId)), - userId, - ); - } else if (tokenResponse.privateKey) { - // User doesn't have masterKeyEncryptedUserKey but they do have a userKeyEncryptedPrivateKey - // This is just existing TDE users or a TDE offboarder on an untrusted device - await this.keyService.setPrivateKey(tokenResponse.privateKey, userId); - } - // else { - // User could be new JIT provisioned SSO user in either a MP encryption org OR a TDE org. - // In either case, the user doesn't yet have a user asymmetric key pair, a user key, or a master key + master key encrypted user key. - // } - } else { - // A user that does not yet have a masterKeyEncryptedUserKey set is a new SSO user - const newSsoUser = tokenResponse.key == null; - - if (!newSsoUser) { - await this.keyService.setPrivateKey( - tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount(userId)), - userId, - ); - } + if (tokenResponse.hasMasterKeyEncryptedUserKey()) { + // User has masterKeyEncryptedUserKey, so set the userKeyEncryptedPrivateKey + // Note: new JIT provisioned SSO users will not yet have a user asymmetric key pair + // and so we don't want them falling into the createKeyPairForOldAccount flow + await this.keyService.setPrivateKey( + tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount(userId)), + userId, + ); + } else if (tokenResponse.privateKey) { + // User doesn't have masterKeyEncryptedUserKey but they do have a userKeyEncryptedPrivateKey + // This is just existing TDE users or a TDE offboarder on an untrusted device + await this.keyService.setPrivateKey(tokenResponse.privateKey, userId); } } @@ -431,30 +410,25 @@ export class SsoLoginStrategy extends LoginStrategy { // - UserDecryptionOptions.UsesKeyConnector is undefined. -- they aren't using key connector // - UserKey is not set after successful login -- because automatic decryption is not available // - userKeyEncryptedPrivateKey is set after successful login -- this is the key differentiator between a TDE org user logging into an untrusted device and MP encryption JIT provisioned user logging in for the first time. - const isSetInitialPasswordFlagOn = await this.configService.getFeatureFlag( - FeatureFlag.PM16117_SetInitialPasswordRefactor, + // Why is that the case? Because we set the userKeyEncryptedPrivateKey when we create the userKey, and this is serving as a proxy to tell us that the userKey has been created already (when enrolling in TDE). + const hasUserKeyEncryptedPrivateKey = await firstValueFrom( + this.keyService.userEncryptedPrivateKey$(userId), ); + const hasUserKey = await this.keyService.hasUserKey(userId); - if (isSetInitialPasswordFlagOn) { - const hasUserKeyEncryptedPrivateKey = await firstValueFrom( - this.keyService.userEncryptedPrivateKey$(userId), + // TODO: PM-23491 we should explore consolidating this logic into a flag on the server. It could be set when an org is switched from TDE to MP encryption for each org user. + if ( + !userDecryptionOptions.trustedDeviceOption && + !userDecryptionOptions.hasMasterPassword && + !userDecryptionOptions.keyConnectorOption?.keyConnectorUrl && + hasUserKeyEncryptedPrivateKey && + !hasUserKey + ) { + await this.masterPasswordService.setForceSetPasswordReason( + ForceSetPasswordReason.TdeOffboardingUntrustedDevice, + userId, ); - const hasUserKey = await this.keyService.hasUserKey(userId); - - // TODO: PM-23491 we should explore consolidating this logic into a flag on the server. It could be set when an org is switched from TDE to MP encryption for each org user. - if ( - !userDecryptionOptions.trustedDeviceOption && - !userDecryptionOptions.hasMasterPassword && - !userDecryptionOptions.keyConnectorOption?.keyConnectorUrl && - hasUserKeyEncryptedPrivateKey && - !hasUserKey - ) { - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.TdeOffboardingUntrustedDevice, - userId, - ); - return true; - } + return true; } // Check if user has permission to set password but hasn't yet diff --git a/libs/common/src/admin-console/services/policy/default-policy.service.ts b/libs/common/src/admin-console/services/policy/default-policy.service.ts index 798adf520f2..2d9518ee508 100644 --- a/libs/common/src/admin-console/services/policy/default-policy.service.ts +++ b/libs/common/src/admin-console/services/policy/default-policy.service.ts @@ -89,8 +89,7 @@ export class DefaultPolicyService implements PolicyService { const policies$ = policies ? of(policies) : this.policies$(userId); return policies$.pipe( map((obsPolicies) => { - // TODO: replace with this.combinePoliciesIntoMasterPasswordPolicyOptions(obsPolicies)) once - // FeatureFlag.PM16117_ChangeExistingPasswordRefactor is removed. + // TODO ([PM-23777]): replace with this.combinePoliciesIntoMasterPasswordPolicyOptions(obsPolicies)) let enforcedOptions: MasterPasswordPolicyOptions | undefined = undefined; const filteredPolicies = obsPolicies.filter((p) => p.type === PolicyType.MasterPassword) ?? []; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 33ded4a22c8..ad61cb421cd 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -14,8 +14,6 @@ export enum FeatureFlag { CreateDefaultLocation = "pm-19467-create-default-location", /* Auth */ - PM16117_SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor", - PM16117_ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor", PM14938_BrowserExtensionLoginApproval = "pm-14938-browser-extension-login-approvals", /* Autofill */ @@ -107,8 +105,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM22136_SdkCipherEncryption]: FALSE, /* Auth */ - [FeatureFlag.PM16117_SetInitialPasswordRefactor]: FALSE, - [FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE, [FeatureFlag.PM14938_BrowserExtensionLoginApproval]: FALSE, /* Billing */