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" },