diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index fc8356505f5..4d8deb5b73f 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -39,6 +39,7 @@ import { TwoFactorAuthGuard, NewDeviceVerificationComponent, DeviceVerificationIcon, + ChangePasswordComponent, } from "@bitwarden/auth/angular"; import { LockComponent } from "@bitwarden/key-management-ui"; import { @@ -63,8 +64,8 @@ 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 { 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"; @@ -145,17 +146,62 @@ const routes: Routes = [ data: { titleId: "deleteOrganization" }, }, { - path: "update-temp-password", - component: UpdateTempPasswordComponent, - canActivate: [authGuard], - data: { titleId: "updateTempPassword" } satisfies RouteDataProperties, + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "update-temp-password", + children: [ + { + path: "", + component: ChangePasswordComponent, + }, + ], + data: { + pageIcon: undefined, + hideFooter: true, + } satisfies AnonLayoutWrapperData, + }, + ], }, { - path: "update-password", - component: UpdatePasswordComponent, - canActivate: [authGuard], + component: AnonLayoutWrapperComponent, + path: "", + children: [ + { + path: "update-password", + canActivate: [authGuard], + children: [ + { + path: "", + component: ChangePasswordComponent, + }, + ], + data: { + pageIcon: undefined, + hideFooter: true, + } satisfies AnonLayoutWrapperData, + }, + ], data: { titleId: "updatePassword" } satisfies RouteDataProperties, }, + // { + // path: "update-temp-password", + // component: ChangePasswordComponent, + // data: {} + // }, + // { + // path: "update-temp-password", + // component: UpdateTempPasswordComponent, + // canActivate: [authGuard], + // data: { titleId: "updateTempPassword" } satisfies RouteDataProperties, + // }, + // { + // path: "update-password", + // component: UpdatePasswordComponent, + // canActivate: [authGuard], + // data: { titleId: "updatePassword" } satisfies RouteDataProperties, + // }, { path: "migrate-legacy-encryption", loadComponent: () => 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..c0b072fd8f1 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 @@ -39,6 +39,11 @@ export interface AnonLayoutWrapperData { * 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/change-password/change-password.component.html b/libs/auth/src/angular/change-password/change-password.component.html index 36f3d1cf9ba..c6c2054f750 100644 --- a/libs/auth/src/angular/change-password/change-password.component.html +++ b/libs/auth/src/angular/change-password/change-password.component.html @@ -9,11 +9,13 @@ } 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 43a9e0d89ce..a7b0509107c 100644 --- a/libs/auth/src/angular/change-password/change-password.component.ts +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -1,10 +1,12 @@ 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 { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -15,9 +17,13 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; -import { ToastService } from "@bitwarden/components"; +import { DialogService, ToastService, Translation } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { I18nPipe } from "@bitwarden/ui-common"; +// import { +// AcceptOrganizationInviteService +// } from "@bitwarden/web-vault/src/app/auth/organization-invite/accept-organization.service"; +// import { RouterService } from "@bitwarden/web-vault/src/app/core"; import { InputPasswordComponent, @@ -38,11 +44,13 @@ export class ChangePasswordComponent implements OnInit { activeAccount: Account | null = null; email?: string; - userId?: UserId; + activeUserId?: UserId; masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; initializing = true; userkeyRotationV2 = false; formPromise?: Promise; + secondaryButtonText?: Translation = undefined; + forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; constructor( private accountService: AccountService, @@ -56,26 +64,75 @@ export class ChangePasswordComponent implements OnInit { private policyService: PolicyService, private toastService: ToastService, private syncService: SyncService, + // private routerService: RouterService, + // private acceptOrganizationInviteService: AcceptOrganizationInviteService, + private dialogService: DialogService, + private router: Router, ) {} async ngOnInit() { this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2); 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), + ); + + if ( + this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset || + this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword + ) { + this.secondaryButtonText = { key: "cancel" }; + } else { + this.secondaryButtonText = undefined; + } + this.initializing = false; } + async performSecondaryAction() { + if ( + this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset || + this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword + ) { + await this.logOut(); + } else { + await this.cancel(); + } + } + + 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 cancel() { + // clearing the login redirect url so that the user + // does not join the organization if they cancel + // await this.routerService.getAndClearLoginRedirectUrl(); + // await this.acceptOrganizationInviteService.clearOrganizationInvitation(); + // await this.router.navigate(["/vault"]); + } + async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { if (this.userkeyRotationV2) { await this.submitNew(passwordInputResult); @@ -104,11 +161,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", @@ -128,7 +185,7 @@ export class ChangePasswordComponent implements OnInit { } private async submitOld(passwordInputResult: PasswordInputResult) { - if (!this.userId) { + if (!this.activeUserId) { throw new Error("userId not found"); } @@ -165,11 +222,11 @@ export class ChangePasswordComponent implements OnInit { // we need to save this for local masterkey verification during rotation await this.masterPasswordService.setMasterKeyHash( passwordInputResult.newLocalMasterKeyHash, - this.userId as UserId, + this.activeUserId as UserId, ); await this.masterPasswordService.setMasterKey( passwordInputResult.newMasterKey, - this.userId as UserId, + this.activeUserId as UserId, ); return this.updateKey(passwordInputResult.newPassword); });