diff --git a/jslib b/jslib index 92a65b7b..009f69fc 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 8169f6a7..72e7d4f9 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 00000000..46bf988d --- /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 00000000..034d1158 --- /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 74b55fc1..ea1b24cd 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 967849e3..50f58546 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 894948cb..49e80b2d 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" },