Skip to content

Commit c3130af

Browse files
feat: add IsStrongPassword decorator (#1025)
1 parent 2bb7d02 commit c3130af

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,7 @@ isBoolean(value);
899899
| `@IsISSN(options?: IsISSNOptions)` | Checks if the string is a ISSN. |
900900
| `@IsISRC()` | Checks if the string is a [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code). |
901901
| `@IsRFC3339()` | Checks if the string is a valid [RFC 3339](https://tools.ietf.org/html/rfc3339) date. |
902+
| `@IsStrongPassword(options?: IsStrongPasswordOptions)` | Checks if the string is a strong password. |
902903
| **Array validation decorators** | |
903904
| `@ArrayContains(values: any[])` | Checks if array contains all values from the given array of values. |
904905
| `@ArrayNotContains(values: any[])` | Checks if array does not contain any of the given values. |

src/decorator/decorators.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export * from './string/IsPostalCode';
111111
export * from './string/IsRFC3339';
112112
export * from './string/IsRgbColor';
113113
export * from './string/IsSemVer';
114+
export * from './string/IsStrongPassword';
114115
export * from './string/IsTimeZone';
115116

116117
// -------------------------------------------------------------------------
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import validator from 'validator';
2+
import { ValidationOptions } from '../ValidationOptions';
3+
import { buildMessage, ValidateBy } from '../common/ValidateBy';
4+
5+
export const IS_STRONG_PASSWORD = 'isStrongPassword';
6+
7+
/**
8+
* Options to be passed to IsStrongPassword decorator.
9+
*/
10+
export type IsStrongPasswordOptions = Pick<
11+
validator.StrongPasswordOptions,
12+
'minLength' | 'minLowercase' | 'minUppercase' | 'minNumbers' | 'minSymbols'
13+
>;
14+
15+
/**
16+
* Checks if the string is a strong password.
17+
* If given value is not a string, then it returns false.
18+
*/
19+
export function isStrongPassword(value: unknown, options?: IsStrongPasswordOptions): boolean {
20+
return typeof value === 'string' && validator.isStrongPassword(value, options);
21+
}
22+
23+
/**
24+
* Checks if the string is a strong password.
25+
* If given value is not a string, then it returns false.
26+
*/
27+
export function IsStrongPassword(
28+
options?: IsStrongPasswordOptions,
29+
validationOptions?: ValidationOptions
30+
): PropertyDecorator {
31+
return ValidateBy(
32+
{
33+
name: IS_STRONG_PASSWORD,
34+
constraints: [options],
35+
validator: {
36+
validate: (value, args): boolean => isStrongPassword(value, args.constraints[0]),
37+
defaultMessage: buildMessage(eachPrefix => eachPrefix + '$property is not strong enough', validationOptions),
38+
},
39+
},
40+
validationOptions
41+
);
42+
}

test/functional/validation-functions-and-decorators.spec.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ import {
184184
isPostalCode,
185185
IsSemVer,
186186
isSemVer,
187+
IsStrongPassword,
188+
isStrongPassword,
189+
IsStrongPasswordOptions,
187190
IsTimeZone,
188191
} from '../../src/decorator/decorators';
189192
import { Validator } from '../../src/validation/Validator';
@@ -4577,3 +4580,83 @@ describe('isInstance', () => {
45774580
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
45784581
});
45794582
});
4583+
4584+
describe('IsStrongPassword', () => {
4585+
class MyClass {
4586+
@IsStrongPassword()
4587+
someProperty: string;
4588+
}
4589+
4590+
const validValues = ['Abcdef1!'];
4591+
const invalidValues = [null, undefined, 'Abcde1!', 'abcdef1!', 'ABCDEF1!', 'Abcdefg!', 'Abcdefg1'];
4592+
4593+
it('should not fail if validator.validate said that its valid', () => {
4594+
return checkValidValues(new MyClass(), validValues);
4595+
});
4596+
4597+
it('should fail if validator.validate said that its invalid', () => {
4598+
return checkInvalidValues(new MyClass(), invalidValues);
4599+
});
4600+
4601+
it('should not fail if method in validator said that its valid', () => {
4602+
validValues.forEach(value => expect(isStrongPassword(value)).toBeTruthy());
4603+
});
4604+
4605+
it('should fail if method in validator said that its invalid', () => {
4606+
invalidValues.forEach(value => expect(isStrongPassword(value)).toBeFalsy());
4607+
});
4608+
4609+
it('should return error object with proper data', () => {
4610+
const validationType = 'isStrongPassword';
4611+
const message = 'someProperty is not strong enough';
4612+
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
4613+
});
4614+
});
4615+
4616+
describe('IsStrongPassword with options', () => {
4617+
const options: IsStrongPasswordOptions = {
4618+
minLength: 12,
4619+
minLowercase: 2,
4620+
minUppercase: 2,
4621+
minNumbers: 2,
4622+
minSymbols: 2,
4623+
};
4624+
4625+
class MyClass {
4626+
@IsStrongPassword(options)
4627+
someProperty: string;
4628+
}
4629+
4630+
const validValues = ['ABcdefgh12!#'];
4631+
const invalidValues = [
4632+
null,
4633+
undefined,
4634+
'ABcdefg12!#',
4635+
'Abcdefgh12!#',
4636+
'ABcDEFGH12!#',
4637+
'ABcdefghi1!#',
4638+
'ABcdefghi12!',
4639+
];
4640+
4641+
it('should not fail if validator.validate said that its valid', () => {
4642+
return checkValidValues(new MyClass(), validValues);
4643+
});
4644+
4645+
it('should fail if validator.validate said that its invalid', () => {
4646+
return checkInvalidValues(new MyClass(), invalidValues);
4647+
});
4648+
4649+
it('should not fail if method in validator said that its valid', () => {
4650+
validValues.forEach(value => expect(isStrongPassword(value, options)).toBeTruthy());
4651+
});
4652+
4653+
it('should fail if method in validator said that its invalid', () => {
4654+
invalidValues.forEach(value => expect(isStrongPassword(value, options)).toBeFalsy());
4655+
});
4656+
4657+
it('should return error object with proper data', () => {
4658+
const validationType = 'isStrongPassword';
4659+
const message = 'someProperty is not strong enough';
4660+
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
4661+
});
4662+
});

0 commit comments

Comments
 (0)