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 @@