From f45e2d7d8a87a46c8e66820bf4a658a4d3f2bc77 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Wed, 21 May 2025 14:55:31 -0400 Subject: [PATCH] feat(change-password-component): Change Password Update [18720] - Very close to complete. --- .../login/web-login-component.service.ts | 2 +- .../password-settings.component.html | 1 - apps/web/src/app/oss-routing.module.ts | 45 ++++++++++++++++- apps/web/src/locales/en/messages.json | 3 ++ .../anon-layout-wrapper.component.html | 1 + .../anon-layout-wrapper.component.ts | 9 +++- .../anon-layout/anon-layout.component.html | 9 +++- .../anon-layout/anon-layout.component.ts | 20 ++++---- .../change-password.component.html | 4 +- .../change-password.component.ts | 48 ++++++++++++++++--- .../input-password.component.html | 2 + .../input-password.component.ts | 2 + 12 files changed, 121 insertions(+), 25 deletions(-) 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 c644f26dd90..36e7143ccd0 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 @@ -98,7 +98,7 @@ export class WebLoginComponentService const enforcedPasswordPolicyOptions = await firstValueFrom( this.accountService.activeAccount$.pipe( getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), + switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)), ), ); diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html index 94cf08b5871..e5fb7142a1a 100644 --- a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html @@ -3,7 +3,6 @@
- {{ "loggedOutWarning" | i18n }}
diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 0d6ffb88ad6..c95b750cfea 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -10,6 +10,7 @@ import { unauthGuardFn, activeAuthGuard, } from "@bitwarden/angular/auth/guards"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, @@ -38,7 +39,9 @@ import { TwoFactorAuthGuard, NewDeviceVerificationComponent, DeviceVerificationIcon, + ChangePasswordComponent, } from "@bitwarden/auth/angular"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockComponent } from "@bitwarden/key-management-ui"; import { VaultIcons } from "@bitwarden/vault"; @@ -139,16 +142,54 @@ const routes: Routes = [ canActivate: [unauthGuardFn()], data: { titleId: "deleteOrganization" }, }, + { + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "change-password", + children: [ + { + path: "", + component: ChangePasswordComponent, + }, + ], + data: { + pageIcon: LockIcon, + pageTitle: { key: "updateMasterPassword" }, + hideFooter: true, + maxWidth: "lg", + } satisfies AnonLayoutWrapperData, + }, + ], + data: { titleId: "updatePassword" } satisfies RouteDataProperties, + }, { path: "update-temp-password", component: UpdateTempPasswordComponent, - canActivate: [authGuard], + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + true, + "/change-password", + false, + ), + authGuard, + ], data: { titleId: "updateTempPassword" } satisfies RouteDataProperties, }, { path: "update-password", component: UpdatePasswordComponent, - canActivate: [authGuard], + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + true, + "/change-password", + false, + ), + authGuard, + ], data: { titleId: "updatePassword" } satisfies RouteDataProperties, }, { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3bf3ab91c78..034a08bdf52 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1788,6 +1788,9 @@ "loggedOutWarning": { "message": "Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, + "changePasswordWarning": { + "message": "After changing your password, you will need to log in with your new password. Active sessions on other devices will be logged out within one hour." + }, "emailChanged": { "message": "Email saved" }, diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html index 95b1e6cadfe..1ade9ba06a6 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.html @@ -5,6 +5,7 @@ [showReadonlyHostname]="showReadonlyHostname" [maxWidth]="maxWidth" [titleAreaMaxWidth]="titleAreaMaxWidth" + [hideFooter]="hideFooter" > diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 04dc3b6dfd2..5ae2fdbf347 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -34,11 +34,16 @@ export interface AnonLayoutWrapperData { /** * Optional flag to set the max-width of the page. Defaults to 'md' if not provided. */ - maxWidth?: "md" | "3xl"; + maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"; /** * Optional flag to set the max-width of the title area. Defaults to null if not provided. */ titleAreaMaxWidth?: "md"; + + /** + * Optional flag to hide the whole footer. + */ + hideFooter?: boolean; } @Component({ @@ -55,6 +60,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { protected showReadonlyHostname: boolean; protected maxWidth: "md" | "3xl"; protected titleAreaMaxWidth: "md"; + protected hideFooter: boolean; constructor( private router: Router, @@ -106,6 +112,7 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { this.showReadonlyHostname = Boolean(firstChildRouteData["showReadonlyHostname"]); this.maxWidth = firstChildRouteData["maxWidth"]; this.titleAreaMaxWidth = firstChildRouteData["titleAreaMaxWidth"]; + this.hideFooter = Boolean(firstChildRouteData["hideFooter"]); } private listenForServiceDataChanges() { diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index 1e16dba82cc..439087f902a 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -37,7 +37,14 @@
} diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts index 51c4d03d16f..85d421072b1 100644 --- a/libs/auth/src/angular/change-password/change-password.component.ts +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -1,14 +1,18 @@ import { Component, Input, OnInit } 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 { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; +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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; +import { DialogService, ToastService, Translation } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { @@ -30,35 +34,65 @@ export class ChangePasswordComponent implements OnInit { activeAccount: Account | null = null; email?: string; - userId?: UserId; + activeUserId?: UserId; masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; initializing = true; submitting = false; + formPromise?: Promise; + forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; + warningText?: Translation; constructor( private accountService: AccountService, private changePasswordService: ChangePasswordService, + private configService: ConfigService, private i18nService: I18nService, + private masterPasswordService: InternalMasterPasswordServiceAbstraction, private messagingService: MessagingService, private policyService: PolicyService, private toastService: ToastService, private syncService: SyncService, + // private routerService: RouterService, + // private acceptOrganizationInviteService: AcceptOrganizationInviteService, + private dialogService: DialogService, + private router: Router, ) {} async ngOnInit() { this.activeAccount = await firstValueFrom(this.accountService.activeAccount$); - this.userId = this.activeAccount?.id; + this.activeUserId = this.activeAccount?.id; this.email = this.activeAccount?.email; - if (!this.userId) { + if (!this.activeUserId) { throw new Error("userId not found"); } this.masterPasswordPolicyOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$(this.userId), + this.policyService.masterPasswordPolicyOptions$(this.activeUserId), + ); + + this.forceSetPasswordReason = await firstValueFrom( + this.masterPasswordService.forceSetPasswordReason$(this.activeUserId), ); this.initializing = false; + + if (this.masterPasswordPolicyOptions?.enforceOnLogin) { + this.warningText = { key: "masterPasswordInvalidWarning" }; + } + } + + 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"); + } } async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { @@ -83,11 +117,11 @@ export class ChangePasswordComponent implements OnInit { passwordInputResult.newPasswordHint, ); } else { - if (!this.userId) { + if (!this.activeUserId) { throw new Error("userId not found"); } - await this.changePasswordService.changePassword(passwordInputResult, this.userId); + await this.changePasswordService.changePassword(passwordInputResult, this.activeUserId); this.toastService.showToast({ variant: "success", diff --git a/libs/auth/src/angular/input-password/input-password.component.html b/libs/auth/src/angular/input-password/input-password.component.html index 8955a7b40b1..d7742a97876 100644 --- a/libs/auth/src/angular/input-password/input-password.component.html +++ b/libs/auth/src/angular/input-password/input-password.component.html @@ -1,4 +1,6 @@
+ {{ "changePasswordWarning" | i18n }} +