Skip to content

Commit

Permalink
[NEW] Implement a local password policy (#9857)
Browse files Browse the repository at this point in the history
* Implement a local password policy

* Improve ValidatePasswordPolicy and create tests

* Validate user’s password on method saveUserProfile

* Fix typo PasswordPoliceClass
  • Loading branch information
graywolf336 authored and rodrigok committed May 20, 2018
1 parent 1070edf commit 3ed656b
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 1 deletion.
27 changes: 27 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,14 @@
"error-no-tokens-for-this-user": "There are no tokens for this user",
"error-not-allowed": "Not allowed",
"error-not-authorized": "Not authorized",
"error-password-policy-not-met": "Password does not meet the server's policy",
"error-password-policy-not-met-minLength": "Password does not meet the server's policy of minimum length (password too short)",
"error-password-policy-not-met-maxLength": "Password does not meet the server's policy of maximum length (password too long)",
"error-password-policy-not-met-repeatingCharacters": "Password not not meet the server's policy of forbidden repeating characters (you have too many of the same characters next to each other)",
"error-password-policy-not-met-oneLowercase": "Password does not meet the server's policy of at least one lowercase character",
"error-password-policy-not-met-oneUppercase": "Password does not meet the server's policy of at least one uppercase character",
"error-password-policy-not-met-oneNumber": "Password does not meet the server's policy of at least one numerical character",
"error-password-policy-not-met-oneSpecial": "Password does not meet the server's policy of at least one special character",
"error-push-disabled": "Push is disabled",
"error-remove-last-owner": "This is the last owner. Please set a new owner before removing this one.",
"error-role-in-use": "Cannot delete role because it's in use",
Expand Down Expand Up @@ -1599,6 +1607,25 @@
"Password": "Password",
"Password_Change_Disabled": "Your Rocket.Chat administrator has disabled the changing of passwords",
"Password_changed_successfully": "Password changed successfully",
"Password_Policy": "Password Policy",
"Accounts_Password_Policy_Enabled": "Enable Password Policy",
"Accounts_Password_Policy_Enabled_Description": "When enabled, user passwords must adhere to the policies set forth. Note: this only applies to new passwords, not existing passwords.",
"Accounts_Password_Policy_MinLength": "Minimum Length",
"Accounts_Password_Policy_MinLength_Description": "Ensures that passwords must have at least this amount of characters. Use `-1` to disable.",
"Accounts_Password_Policy_MaxLength": "Maximum Length",
"Accounts_Password_Policy_MaxLength_Description": "Ensures that passwords do not have more than this amount of characters. Use `-1` to disable.",
"Accounts_Password_Policy_ForbidRepeatingCharacters": "Forbid Repeating Characters",
"Accounts_Password_Policy_ForbidRepeatingCharacters_Description": "Ensures passwords do not contain the same character repeating next to each other.",
"Accounts_Password_Policy_ForbidRepeatingCharactersCount": "Max Repeating Characters",
"Accounts_Password_Policy_ForbidRepeatingCharactersCount_Description": "The amount of times a character can be repeating before it is not allowed.",
"Accounts_Password_Policy_AtLeastOneLowercase": "At Least One Lowercase",
"Accounts_Password_Policy_AtLeastOneLowercase_Description": "Enforce that a password contain at least one lowercase character.",
"Accounts_Password_Policy_AtLeastOneUppercase": "At Least One Uppercase",
"Accounts_Password_Policy_AtLeastOneUppercase_Description": "Enforce that a password contain at least one lowercase character.",
"Accounts_Password_Policy_AtLeastOneNumber": "At Least One Number",
"Accounts_Password_Policy_AtLeastOneNumber_Description": "Enforce that a password contain at least one numerical character.",
"Accounts_Password_Policy_AtLeastOneSpecialCharacter": "At Least One Symbol",
"Accounts_Password_Policy_AtLeastOneSpecialCharacter_Description": "Enforce that a password contain at least one special character.",
"Past_Chats": "Past Chats",
"Payload": "Payload",
"People": "People",
Expand Down
1 change: 1 addition & 0 deletions packages/rocketchat-lib/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Package.onUse(function(api) {
api.addFiles('server/lib/roomTypes.js', 'server');
api.addFiles('server/lib/sendNotificationsOnMessage.js', 'server');
api.addFiles('server/lib/validateEmailDomain.js', 'server');
api.addFiles('server/lib/passwordPolicy.js', 'server');

// SERVER MODELS
api.addFiles('server/models/_Base.js', 'server');
Expand Down
2 changes: 1 addition & 1 deletion packages/rocketchat-lib/server/functions/saveUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ RocketChat.saveUser = function(userId, userData) {
RocketChat.setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser);
}

if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password') && RocketChat.passwordPolicy.validate(userData.password)) {
Accounts.setPassword(userData._id, userData.password.trim());
}

Expand Down
91 changes: 91 additions & 0 deletions packages/rocketchat-lib/server/lib/PasswordPolicyClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
class PasswordPolicy {
constructor({
enabled = false,
minLength = -1,
maxLength = -1,
forbidRepeatingCharacters = false,
forbidRepeatingCharactersCount = 3, //the regex is this number minus one
mustContainAtLeastOneLowercase = false, // /[A-Z]{3,}/ could do this instead of at least one
mustContainAtLeastOneUppercase = false,
mustContainAtLeastOneNumber = false,
mustContainAtLeastOneSpecialCharacter = false,
throwError = true
} = {}) {
this.regex = {
mustContainAtLeastOneLowercase: new RegExp('[a-z]'),
mustContainAtLeastOneUppercase: new RegExp('[A-Z]'),
mustContainAtLeastOneNumber: new RegExp('[0-9]'),
mustContainAtLeastOneSpecialCharacter: new RegExp('[^A-Za-z0-9 ]')
};

this.enabled = enabled;
this.minLength = minLength;
this.maxLength = maxLength;
this.forbidRepeatingCharacters = forbidRepeatingCharacters;
this.forbidRepeatingCharactersCount = forbidRepeatingCharactersCount;
this.mustContainAtLeastOneLowercase = mustContainAtLeastOneLowercase;
this.mustContainAtLeastOneUppercase = mustContainAtLeastOneUppercase;
this.mustContainAtLeastOneNumber = mustContainAtLeastOneNumber;
this.mustContainAtLeastOneSpecialCharacter = mustContainAtLeastOneSpecialCharacter;
this.throwError = throwError;
}

set forbidRepeatingCharactersCount(value) {
this._forbidRepeatingCharactersCount = value;
this.regex.forbiddingRepeatingCharacters = new RegExp(`(.)\\1{${ this.forbidRepeatingCharactersCount },}`);
}

get forbidRepeatingCharactersCount() {
return this._forbidRepeatingCharactersCount;
}

error(error, message) {
if (this.throwError) {
throw new Meteor.Error(error, message);
}

return false;
}

validate(password) {
if (!this.enabled) {
return true;
}

if (!password || typeof password !== 'string' || !password.length) {
return this.error('error-password-policy-not-met', 'The password provided does not meet the server\'s password policy.');
}

if (this.minLength >= 1 && password.length < this.minLength) {
return this.error('error-password-policy-not-met-minLength', 'The password does not meet the minimum length password policy.');
}

if (this.maxLength >= 1 && password.length > this.maxLength) {
return this.error('error-password-policy-not-met-maxLength', 'The password does not meet the maximum length password policy.');
}

if (this.forbidRepeatingCharacters && this.regex.forbiddingRepeatingCharacters.test(password)) {
return this.error('error-password-policy-not-met-repeatingCharacters', 'The password contains repeating characters which is against the password policy.');
}

if (this.mustContainAtLeastOneLowercase && !this.regex.mustContainAtLeastOneLowercase.test(password)) {
return this.error('error-password-policy-not-met-oneLowercase', 'The password does not contain at least one lowercase character which is against the password policy.');
}

if (this.mustContainAtLeastOneUppercase && !this.regex.mustContainAtLeastOneUppercase.test(password)) {
return this.error('error-password-policy-not-met-oneUppercase', 'The password does not contain at least one uppercase character which is against the password policy.');
}

if (this.mustContainAtLeastOneNumber && !this.regex.mustContainAtLeastOneNumber.test(password)) {
return this.error('error-password-policy-not-met-oneNumber', 'The password does not contain at least one numerical character which is against the password policy.');
}

if (this.mustContainAtLeastOneSpecialCharacter && !this.regex.mustContainAtLeastOneSpecialCharacter.test(password)) {
return this.error('error-password-policy-not-met-oneSpecial', 'The password does not contain at least one special character which is against the password policy.');
}

return true;
}
}

export default PasswordPolicy;
13 changes: 13 additions & 0 deletions packages/rocketchat-lib/server/lib/passwordPolicy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import PasswordPolicy from './PasswordPolicyClass';

RocketChat.passwordPolicy = new PasswordPolicy();

RocketChat.settings.get('Accounts_Password_Policy_Enabled', (key, value) => RocketChat.passwordPolicy.enabled = value);
RocketChat.settings.get('Accounts_Password_Policy_MinLength', (key, value) => RocketChat.passwordPolicy.minLength = value);
RocketChat.settings.get('Accounts_Password_Policy_MaxLength', (key, value) => RocketChat.passwordPolicy.maxLength = value);
RocketChat.settings.get('Accounts_Password_Policy_ForbidRepeatingCharacters', (key, value) => RocketChat.passwordPolicy.forbidRepeatingCharacters = value);
RocketChat.settings.get('Accounts_Password_Policy_ForbidRepeatingCharactersCount', (key, value) => RocketChat.passwordPolicy.forbidRepeatingCharactersCount = value);
RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneLowercase', (key, value) => RocketChat.passwordPolicy.mustContainAtLeastOneLowercase = value);
RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneUppercase', (key, value) => RocketChat.passwordPolicy.mustContainAtLeastOneUppercase = value);
RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneNumber', (key, value) => RocketChat.passwordPolicy.mustContainAtLeastOneNumber = value);
RocketChat.settings.get('Accounts_Password_Policy_AtLeastOneSpecialCharacter', (key, value) => RocketChat.passwordPolicy.mustContainAtLeastOneSpecialCharacter = value);
51 changes: 51 additions & 0 deletions packages/rocketchat-lib/server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,57 @@ RocketChat.settings.addGroup('Accounts', function() {
type: 'boolean'
});
});

this.section('Password_Policy', function() {
this.add('Accounts_Password_Policy_Enabled', false, {
type: 'boolean'
});

const enableQuery = {
_id: 'Accounts_Password_Policy_Enabled',
value: true
};

this.add('Accounts_Password_Policy_MinLength', 7, {
type: 'int',
enableQuery
});

this.add('Accounts_Password_Policy_MaxLength', -1, {
type: 'int',
enableQuery
});

this.add('Accounts_Password_Policy_ForbidRepeatingCharacters', true, {
type: 'boolean',
enableQuery
});

this.add('Accounts_Password_Policy_ForbidRepeatingCharactersCount', 3, {
type: 'int',
enableQuery
});

this.add('Accounts_Password_Policy_AtLeastOneLowercase', true, {
type: 'boolean',
enableQuery
});

this.add('Accounts_Password_Policy_AtLeastOneUppercase', true, {
type: 'boolean',
enableQuery
});

this.add('Accounts_Password_Policy_AtLeastOneNumber', true, {
type: 'boolean',
enableQuery
});

this.add('Accounts_Password_Policy_AtLeastOneSpecialCharacter', true, {
type: 'boolean',
enableQuery
});
});
});

RocketChat.settings.addGroup('OAuth', function() {
Expand Down
Loading

0 comments on commit 3ed656b

Please sign in to comment.