diff --git a/README.md b/README.md index b2896b73f..ae39d74ec 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ Validator | Description **isISO31661Alpha3(str)** | check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code. **isISRC(str)** | check if the string is a [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code). **isISSN(str [, options])** | check if the string is an [ISSN](https://en.wikipedia.org/wiki/International_Standard_Serial_Number).

`options` is an object which defaults to `{ case_sensitive: false, require_hyphen: false }`. If `case_sensitive` is true, ISSNs with a lowercase `'x'` as the check digit are rejected. +**isCPF(str [, options])** | check if the string is a valid brazilian [CPF](https://en.wikipedia.org/wiki/CPF_number). The CPF is analogous to the USA's SSN.

`options` is an object which defaults to `{ separators: 'optional' }` which by default validates CPFs with or without separators; other valid values for `separators` are `require` and `none`; **isJSON(str [, options])** | check if the string is valid JSON (note: uses JSON.parse).

`options` is an object which defaults to `{ allow_primitives: false }`. If `allow_primitives` is true, the primitives 'true', 'false' and 'null' are accepted as valid JSON values. **isJWT(str)** | check if the string is valid JWT token. **isLatLong(str [, options])** | check if the string is a valid latitude-longitude coordinate in the format `lat,long` or `lat, long`.

`options` is an object that defaults to `{ checkDMS: false }`. Pass `checkDMS` as `true` to validate DMS(degrees, minutes, and seconds) latitude-longitude format. diff --git a/src/index.js b/src/index.js index e91c582da..bdbadf126 100644 --- a/src/index.js +++ b/src/index.js @@ -76,6 +76,7 @@ import isEAN from './lib/isEAN'; import isISIN from './lib/isISIN'; import isISBN from './lib/isISBN'; import isISSN from './lib/isISSN'; +import isCPF from './lib/isCPF'; import isTaxID from './lib/isTaxID'; import isMobilePhone, { locales as isMobilePhoneLocales } from './lib/isMobilePhone'; @@ -187,6 +188,7 @@ const validator = { isISIN, isISBN, isISSN, + isCPF, isMobilePhone, isMobilePhoneLocales, isPostalCode, diff --git a/src/lib/isCPF.js b/src/lib/isCPF.js new file mode 100644 index 000000000..d435d5b91 --- /dev/null +++ b/src/lib/isCPF.js @@ -0,0 +1,35 @@ +import assertString from './util/assertString'; + +const defaultOptions = { separators: 'optional' }; // 'require', 'none' or 'optional' (default) + +const formatSeparators = /^(\d{3})\.(\d{3})\.(\d{3})-(\d{2})$/; // 000.000.000-00 +const formatPlain = /^(\d{3})(\d{3})(\d{3})(\d{2})$/; // 00000000000 +const formatBoth = /^(\d{3})\.?(\d{3})\.?(\d{3})-?(\d{2})$/; // both formats above, i.e., optional separators + +const weightsFirstDigit = [10, 9, 8, 7, 6, 5, 4, 3, 2]; +const weightsSecondDigit = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]; + +function calculateDigit(values, weights) { + const sum = values.reduce((acc, cur, i) => acc + (cur * weights[i]), 0); + const remainder = sum % 11; + return String(remainder < 2 ? 0 : 11 - remainder); +} + +export default function isCPF(str, options = defaultOptions) { + assertString(str); + const { separators } = { ...defaultOptions, ...options }; + + let format = formatBoth; + if (separators === 'require') format = formatSeparators; + else if (separators === 'none') format = formatPlain; + + const match = format.exec(str); + if (!match) return false; + const cpf = Array.from(match[1] + match[2] + match[3]); + + const digit1 = calculateDigit(cpf, weightsFirstDigit); + const digit2 = calculateDigit(cpf.concat(digit1), weightsSecondDigit); + + const [first, second] = match[4]; + return first === digit1 && second === digit2; +} diff --git a/test/validators.js b/test/validators.js index b9899b626..8fe204ad6 100644 --- a/test/validators.js +++ b/test/validators.js @@ -4758,6 +4758,75 @@ describe('Validators', () => { }); }); + it('should validate CPFs', () => { + test({ + validator: 'isCPF', + valid: [ + '844.186.675-98', + '216.414.064-88', + '391.757.780-17', + '73112521722', + '53253165167', + '24462635183', + ], + invalid: [ + '', + ' ', + '84.4186.675-89', + '216.414.064', + '391 757 780 17', + '73112521729', + '53253165169', + '24462635189', + ], + }); + test({ + validator: 'isCPF', + args: [{ separators: 'require' }], + valid: [ + '844.186.675-98', + '216.414.064-88', + '391.757.780-17', + '179.518.290-30', + ], + invalid: [ + '', + ' ', + '84.4186.675-89', + '216.414.064', + '391 757 780 17', + '73112521722', + '53253165167', + '24462635183', + '73112521729', + '53253165169', + '24462635189', + ], + }); + test({ + validator: 'isCPF', + args: [{ separators: 'none' }], + valid: [ + '73112521722', + '53253165167', + '24462635183', + ], + invalid: [ + '', + ' ', + '84.4186.675-89', + '216.414.064', + '391 757 780 17', + '73112521729', + '53253165169', + '24462635189', + '844.186.675-98', + '216.414.064-88', + '391.757.780-17', + ], + }); + }); + it('should validate JSON', () => { test({ validator: 'isJSON',