From 6210396aa963667095b898fd63645899d84a28db Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Mon, 2 Mar 2020 11:05:05 -0600 Subject: [PATCH] Enforce master password policy (#79) * Enforce master password policy * Updated based on requested changes/discussions --- src/abstractions/policy.service.ts | 6 +- .../domain/masterPasswordPolicyOptions.ts | 10 +++ src/services/policy.service.ts | 86 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/models/domain/masterPasswordPolicyOptions.ts diff --git a/src/abstractions/policy.service.ts b/src/abstractions/policy.service.ts index b08269644a..e281fb5855 100644 --- a/src/abstractions/policy.service.ts +++ b/src/abstractions/policy.service.ts @@ -1,6 +1,7 @@ import { PolicyData } from '../models/data/policyData'; -import { Policy } from '../models/domain/policy'; +import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions' +import { Policy } from '../models/domain/policy' import { PolicyType } from '../enums/policyType'; @@ -11,4 +12,7 @@ export abstract class PolicyService { getAll: (type?: PolicyType) => Promise; replace: (policies: { [id: string]: PolicyData; }) => Promise; clear: (userId: string) => Promise; + getMasterPasswordPolicyOptions: () => Promise; + evaluateMasterPassword: (passwordStrength: number, newPassword: string, + enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean; } diff --git a/src/models/domain/masterPasswordPolicyOptions.ts b/src/models/domain/masterPasswordPolicyOptions.ts new file mode 100644 index 0000000000..ed04a455bf --- /dev/null +++ b/src/models/domain/masterPasswordPolicyOptions.ts @@ -0,0 +1,10 @@ +import Domain from './domainBase'; + +export class MasterPasswordPolicyOptions extends Domain { + minComplexity: number = 0; + minLength: number = 0; + requireUpper: boolean = false; + requireLower: boolean = false; + requireNumbers: boolean = false; + requireSpecial: boolean = false; +} diff --git a/src/services/policy.service.ts b/src/services/policy.service.ts index 2baac07678..040b23ba76 100644 --- a/src/services/policy.service.ts +++ b/src/services/policy.service.ts @@ -5,6 +5,7 @@ import { UserService } from '../abstractions/user.service'; import { PolicyData } from '../models/data/policyData'; import { Policy } from '../models/domain/policy'; +import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions' import { PolicyType } from '../enums/policyType'; @@ -52,4 +53,89 @@ export class PolicyService implements PolicyServiceAbstraction { await this.storageService.remove(Keys.policiesPrefix + userId); this.policyCache = null; } + + async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise { + let enforcedOptions: MasterPasswordPolicyOptions = null; + + if (policies == null) { + policies = await this.getAll(PolicyType.MasterPassword); + } else { + policies = policies.filter((p) => p.type === PolicyType.MasterPassword); + } + + if (policies == null || policies.length === 0) { + return enforcedOptions; + } + + policies.forEach((currentPolicy) => { + if (!currentPolicy.enabled || currentPolicy.data == null) { + return; + } + + if (enforcedOptions == null) { + enforcedOptions = new MasterPasswordPolicyOptions(); + } + + if (currentPolicy.data.minComplexity != null + && currentPolicy.data.minComplexity > enforcedOptions.minComplexity) { + enforcedOptions.minComplexity = currentPolicy.data.minComplexity; + } + + if (currentPolicy.data.minLength != null + && currentPolicy.data.minLength > enforcedOptions.minLength) { + enforcedOptions.minLength = currentPolicy.data.minLength; + } + + if (currentPolicy.data.requireUpper) { + enforcedOptions.requireUpper = true; + } + + if (currentPolicy.data.requireLower) { + enforcedOptions.requireLower = true; + } + + if (currentPolicy.data.requireNumbers) { + enforcedOptions.requireNumbers = true; + } + + if (currentPolicy.data.requireSpecial) { + enforcedOptions.requireSpecial = true; + } + }); + + return enforcedOptions; + } + + evaluateMasterPassword(passwordStrength: number, newPassword: string, + enforcedPolicyOptions: MasterPasswordPolicyOptions): boolean { + if (enforcedPolicyOptions == null) { + return true; + } + + if (enforcedPolicyOptions.minComplexity > 0 && enforcedPolicyOptions.minComplexity > passwordStrength) { + return false; + } + + if (enforcedPolicyOptions.minLength > 0 && enforcedPolicyOptions.minLength > newPassword.length) { + return false; + } + + if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) { + return false; + } + + if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) { + return false; + } + + if (enforcedPolicyOptions.requireNumbers && !(/[0-9]/.test(newPassword))) { + return false; + } + + if (enforcedPolicyOptions.requireSpecial && !(/[!@#$%\^&*]/g.test(newPassword))) { + return false; + } + + return true; + } }