mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 12:13:45 +00:00
feat(change-password-component): Change Password Update [18720] - Very close to complete.
This commit is contained in:
@@ -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)),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
</div>
|
||||
|
||||
<div class="tw-max-w-lg tw-mb-12">
|
||||
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
|
||||
<auth-change-password [inputPasswordFlow]="inputPasswordFlow"></auth-change-password>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[showReadonlyHostname]="showReadonlyHostname"
|
||||
[maxWidth]="maxWidth"
|
||||
[titleAreaMaxWidth]="titleAreaMaxWidth"
|
||||
[hideFooter]="hideFooter"
|
||||
>
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet slot="secondary" name="secondary"></router-outlet>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -37,7 +37,14 @@
|
||||
|
||||
<div
|
||||
class="tw-grow tw-w-full tw-max-w-md tw-mx-auto tw-flex tw-flex-col tw-items-center sm:tw-min-w-[28rem]"
|
||||
[ngClass]="{ 'tw-max-w-md': maxWidth === 'md', 'tw-max-w-3xl': maxWidth === '3xl' }"
|
||||
[ngClass]="{
|
||||
'tw-max-w-sm': maxWidth === 'sm',
|
||||
'tw-max-w-md': maxWidth === 'md',
|
||||
'tw-max-w-lg': maxWidth === 'lg',
|
||||
'tw-max-w-xl': maxWidth === 'xl',
|
||||
'tw-max-w-2xl': maxWidth === '2xl',
|
||||
'tw-max-w-3xl': maxWidth === '3xl',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="tw-rounded-2xl tw-mb-6 sm:tw-mb-10 tw-mx-auto tw-w-full sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, HostBinding, Input, OnChanges, OnInit, SimpleChanges } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
@@ -33,10 +31,10 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
return ["tw-h-full"];
|
||||
}
|
||||
|
||||
@Input() title: string;
|
||||
@Input() subtitle: string;
|
||||
@Input() icon: Icon;
|
||||
@Input() showReadonlyHostname: boolean;
|
||||
@Input() title?: string;
|
||||
@Input() subtitle?: string;
|
||||
@Input() icon?: Icon;
|
||||
@Input() showReadonlyHostname?: boolean;
|
||||
@Input() hideLogo: boolean = false;
|
||||
@Input() hideFooter: boolean = false;
|
||||
@Input() hideIcon: boolean = false;
|
||||
@@ -53,13 +51,13 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
*
|
||||
* @default 'md'
|
||||
*/
|
||||
@Input() maxWidth: "md" | "3xl" = "md";
|
||||
@Input() maxWidth: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" = "md";
|
||||
|
||||
protected logo = BitwardenLogo;
|
||||
protected year = "2024";
|
||||
protected year: string;
|
||||
protected clientType: ClientType;
|
||||
protected hostname: string;
|
||||
protected version: string;
|
||||
protected hostname?: string;
|
||||
protected version?: string;
|
||||
|
||||
protected hideYearAndVersion = false;
|
||||
|
||||
@@ -74,7 +72,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
|
||||
async ngOnInit() {
|
||||
this.maxWidth = this.maxWidth ?? "md";
|
||||
this.titleAreaMaxWidth = this.titleAreaMaxWidth ?? null;
|
||||
this.titleAreaMaxWidth = this.titleAreaMaxWidth ?? undefined;
|
||||
this.hostname = (await firstValueFrom(this.environmentService.environment$)).getHostname();
|
||||
this.version = await this.platformUtilsService.getApplicationVersion();
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
<auth-input-password
|
||||
[flow]="inputPasswordFlow"
|
||||
[email]="email"
|
||||
[userId]="userId"
|
||||
[userId]="activeUserId"
|
||||
[loading]="submitting"
|
||||
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
||||
[inlineButtons]="true"
|
||||
[primaryButtonText]="{ key: 'changeMasterPassword' }"
|
||||
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
|
||||
[secondaryButtonText]="{ key: 'cancel' }"
|
||||
(onSecondaryButtonClick)="logOut()"
|
||||
>
|
||||
</auth-input-password>
|
||||
}
|
||||
|
||||
@@ -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<any>;
|
||||
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",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-callout type="warning">{{ "changePasswordWarning" | i18n }}</bit-callout>
|
||||
|
||||
<auth-password-callout
|
||||
*ngIf="masterPasswordPolicyOptions"
|
||||
[policy]="masterPasswordPolicyOptions"
|
||||
|
||||
@@ -20,6 +20,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutComponent,
|
||||
CheckboxModule,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
@@ -86,6 +87,7 @@ interface InputPasswordForm {
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutComponent,
|
||||
CheckboxModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
|
||||
Reference in New Issue
Block a user