From 596c3e86e9a44ca524bc76e5947f1992b5412d3c Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Thu, 3 Feb 2022 00:00:57 -0500 Subject: [PATCH] Master password policy is not checked when accepting invite from an existing account (#1371) * validate password against org policy and create update-password component * linting and prettier * [bug] Default rememberEmail to true (#1429) * switching the dashes to underscores for the branch name (#1433) (cherry picked from commit 8910430dfb191c0333f33f261523fe7b90c0d7af) * fix merge conflicts * Update src/app/accounts/update-password.component.html Co-authored-by: Justin Baur * Update src/locales/en/messages.json Co-authored-by: Justin Baur * update jslib * prettier Co-authored-by: Addison Beck Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: Justin Baur --- jslib | 2 +- src/app/accounts/login.component.ts | 57 +++++++++++- .../accounts/update-password.component.html | 90 +++++++++++++++++++ src/app/accounts/update-password.component.ts | 52 +++++++++++ src/app/oss-routing.module.ts | 7 ++ src/app/oss.module.ts | 2 + src/locales/en/messages.json | 3 + 7 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/app/accounts/update-password.component.html create mode 100644 src/app/accounts/update-password.component.ts diff --git a/jslib b/jslib index 92a65b7b368..009f69fcb1f 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 92a65b7b368a8dbf55350657674c90169b04c30b +Subproject commit 009f69fcb1fc2168f015e5bc6de3a8583cbfe5fd diff --git a/src/app/accounts/login.component.ts b/src/app/accounts/login.component.ts index 8169f6a773e..72e7d4f9af5 100644 --- a/src/app/accounts/login.component.ts +++ b/src/app/accounts/login.component.ts @@ -17,7 +17,11 @@ import { StateService } from "../../abstractions/state.service"; import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component"; +import { PolicyData } from "jslib-common/models/data/policyData"; +import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; import { Policy } from "jslib-common/models/domain/policy"; +import { ListResponse } from "jslib-common/models/response/listResponse"; +import { PolicyResponse } from "jslib-common/models/response/policyResponse"; @Component({ selector: "app-login", @@ -25,6 +29,8 @@ import { Policy } from "jslib-common/models/domain/policy"; }) export class LoginComponent extends BaseLoginComponent { showResetPasswordAutoEnrollWarning = false; + enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; + policies: ListResponse; constructor( authService: AuthService, @@ -86,29 +92,57 @@ export class LoginComponent extends BaseLoginComponent { if (invite != null) { let policyList: Policy[] = null; try { - const policies = await this.apiService.getPoliciesByToken( + this.policies = await this.apiService.getPoliciesByToken( invite.organizationId, invite.token, invite.email, invite.organizationUserId ); - policyList = this.policyService.mapPoliciesFromToken(policies); + policyList = this.policyService.mapPoliciesFromToken(this.policies); } catch (e) { this.logService.error(e); } if (policyList != null) { - const result = this.policyService.getResetPasswordPolicyOptions( + const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( policyList, invite.organizationId ); // Set to true if policy enabled and auto-enroll enabled - this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled; + this.showResetPasswordAutoEnrollWarning = + resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; + + this.enforcedPasswordPolicyOptions = + await this.policyService.getMasterPasswordPolicyOptions(policyList); } } } async goAfterLogIn() { + // Check master password against policy + if (this.enforcedPasswordPolicyOptions != null) { + const strengthResult = this.passwordGenerationService.passwordStrength( + this.masterPassword, + this.getPasswordStrengthUserInput() + ); + const masterPasswordScore = strengthResult == null ? null : strengthResult.score; + + // If invalid, save policies and require update + if ( + !this.policyService.evaluateMasterPassword( + masterPasswordScore, + this.masterPassword, + this.enforcedPasswordPolicyOptions + ) + ) { + const policiesData: { [id: string]: PolicyData } = {}; + this.policies.data.map((p) => (policiesData[p.id] = new PolicyData(p))); + await this.policyService.replace(policiesData); + this.router.navigate(["update-password"]); + return; + } + } + const loginRedirect = await this.stateService.getLoginRedirect(); if (loginRedirect != null) { this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams }); @@ -125,4 +159,19 @@ export class LoginComponent extends BaseLoginComponent { } await super.submit(); } + + private getPasswordStrengthUserInput() { + let userInput: string[] = []; + const atPosition = this.email.indexOf("@"); + if (atPosition > -1) { + userInput = userInput.concat( + this.email + .substr(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + ); + } + return userInput; + } } diff --git a/src/app/accounts/update-password.component.html b/src/app/accounts/update-password.component.html new file mode 100644 index 00000000000..46bf988d9b9 --- /dev/null +++ b/src/app/accounts/update-password.component.html @@ -0,0 +1,90 @@ +
+
+
+

{{ "updateMasterPassword" | i18n }}

+
+
+ {{ "masterPasswordInvalidWarning" | i18n }} + + + +
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+ + + +
+
+
+
+ diff --git a/src/app/accounts/update-password.component.ts b/src/app/accounts/update-password.component.ts new file mode 100644 index 00000000000..034d1158f89 --- /dev/null +++ b/src/app/accounts/update-password.component.ts @@ -0,0 +1,52 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { first } from "rxjs/operators"; + +import { ApiService } from "jslib-common/abstractions/api.service"; +import { CryptoService } from "jslib-common/abstractions/crypto.service"; +import { I18nService } from "jslib-common/abstractions/i18n.service"; +import { LogService } from "jslib-common/abstractions/log.service"; +import { MessagingService } from "jslib-common/abstractions/messaging.service"; +import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; +import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; +import { PolicyService } from "jslib-common/abstractions/policy.service"; +import { SyncService } from "jslib-common/abstractions/sync.service"; + +import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "jslib-angular/components/update-password.component"; +import { StateService } from "jslib-common/abstractions/state.service"; +import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; + +@Component({ + selector: "app-update-password", + templateUrl: "update-password.component.html", +}) +export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { + constructor( + router: Router, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + passwordGenerationService: PasswordGenerationService, + policyService: PolicyService, + cryptoService: CryptoService, + messagingService: MessagingService, + apiService: ApiService, + logService: LogService, + stateService: StateService, + userVerificationService: UserVerificationService + ) { + super( + router, + i18nService, + platformUtilsService, + passwordGenerationService, + policyService, + cryptoService, + messagingService, + apiService, + stateService, + userVerificationService, + logService + ); + } +} diff --git a/src/app/oss-routing.module.ts b/src/app/oss-routing.module.ts index 74b55fc1b0c..ea1b24cdbcf 100644 --- a/src/app/oss-routing.module.ts +++ b/src/app/oss-routing.module.ts @@ -17,6 +17,7 @@ import { RemovePasswordComponent } from "./accounts/remove-password.component"; import { SetPasswordComponent } from "./accounts/set-password.component"; import { SsoComponent } from "./accounts/sso.component"; import { TwoFactorComponent } from "./accounts/two-factor.component"; +import { UpdatePasswordComponent } from "./accounts/update-password.component"; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component"; @@ -162,6 +163,12 @@ const routes: Routes = [ canActivate: [AuthGuardService], data: { titleId: "updateTempPassword" }, }, + { + path: "update-password", + component: UpdatePasswordComponent, + canActivate: [AuthGuardService], + data: { titleId: "updatePassword" }, + }, { path: "remove-password", component: RemovePasswordComponent, diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts index 967849e3520..50f58546035 100644 --- a/src/app/oss.module.ts +++ b/src/app/oss.module.ts @@ -30,6 +30,7 @@ import { SetPasswordComponent } from "./accounts/set-password.component"; import { SsoComponent } from "./accounts/sso.component"; import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component"; import { TwoFactorComponent } from "./accounts/two-factor.component"; +import { UpdatePasswordComponent } from "./accounts/update-password.component"; import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./accounts/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.component"; @@ -431,6 +432,7 @@ registerLocaleData(localeZhTw, "zh-TW"); UpdateKeyComponent, UpdateLicenseComponent, UpdateTempPasswordComponent, + UpdatePasswordComponent, UserBillingComponent, UserLayoutComponent, UserSubscriptionComponent, diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 894948cb060..49e80b2dec5 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -4325,6 +4325,9 @@ "updateMasterPasswordWarning": { "message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update your Master Password now. 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." }, + "masterPasswordInvalidWarning": { + "message": "Your Master Password does not meet the policy requirements of this organization. In order to join the organization, you must update your Master Password now. 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." + }, "maximumVaultTimeout": { "message": "Vault Timeout" },