From b0633150b0576c3db0b377e439104d7ebc3bdeeb Mon Sep 17 00:00:00 2001 From: Kelvin <kelvingremista2015@hotmail.com> Date: Wed, 2 Apr 2025 00:10:09 -0300 Subject: [PATCH 1/3] feat: added isCPF validator --- src/index.js | 3 +++ src/lib/isCPF.js | 51 +++++++++++++++++++++++++++++++++++ test/validators.test.js | 59 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/lib/isCPF.js diff --git a/src/index.js b/src/index.js index c47674595..20baffb54 100644 --- a/src/index.js +++ b/src/index.js @@ -130,6 +130,8 @@ import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; +import isCPF from './lib/isCPF'; + const version = '13.15.0'; const validator = { @@ -245,6 +247,7 @@ const validator = { isLicensePlate, isVAT, ibanLocales, + isCPF, }; export default validator; diff --git a/src/lib/isCPF.js b/src/lib/isCPF.js new file mode 100644 index 000000000..23145af82 --- /dev/null +++ b/src/lib/isCPF.js @@ -0,0 +1,51 @@ +import assertString from './util/assertString'; + + +export default function isCPF(cpf) { + assertString(cpf); + if (cpf.length !== 11) return false; + if (cpf === '00000000000' || cpf === '11111111111' || cpf === '22222222222' || cpf === '33333333333' || cpf === '44444444444' || cpf === '55555555555' || cpf === '66666666666' || cpf === '77777777777' || cpf === '88888888888' || cpf === '99999999999') return false; + + const paramD1 = { + 10: 0, 9: 1, 8: 2, 7: 3, 6: 4, 5: 5, 4: 6, 3: 7, 2: 8, + }; + const paramD2 = { + 11: 0, 10: 1, 9: 2, 8: 3, 7: 4, 6: 5, 5: 6, 4: 7, 3: 8, 2: 9, + }; + let firstNineCharacters = cpf.slice(0, 9); + let d1 = 0; + let d2 = 0; + let d1AndD2 = ''; + + for (let i = 10; i >= 2; i--) { + if (Number.isNaN(Number(firstNineCharacters[paramD1[i]]))) return false; + d1 += Number(firstNineCharacters[paramD1[i]]) * i; + } + + if (d1 % 11 < 2) { + d1 = 0; + } else { + d1 = 11 - (d1 % 11); + } + + firstNineCharacters += String(d1); + + for (let i = 11; i >= 2; i--) { + d2 += Number(firstNineCharacters[paramD2[i]]) * i; + } + + if (d2 % 11 < 2) { + d2 = 0; + } else { + d2 = 11 - (d2 % 11); + } + + d1AndD2 += d1; + d1AndD2 += d2; + + if (d1AndD2 === cpf.slice(cpf.length - 2)) { + return true; + } + + return false; +} diff --git a/test/validators.test.js b/test/validators.test.js index d6e948a41..26edaba14 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -15440,4 +15440,63 @@ describe('Validators', () => { ], }); }); + it('should validate a CPF', () => { + test({ + validator: 'isCPF', + valid: [ + '15709954861', + '55734947598', + '60142555835', + '54945762139', + '36718123897', + '43849941922', + '97989392984', + '98273014037', + '66603094703', + '40668005130', + '50699975565', + '98798876465', + '81661188907', + '52991780002', + '03703980761', + '62281573370', + '05575125572', + '03085770860', + '28686002129', + '24234579793', + ], + invalid: [ + '296.231.440-69', + '62424843039', + '477.887.094-96', + '41526890821', + '861.803.242-00', + '57751926579', + '034.928.141-63', + '66437339822', + '178.714.111-57', + '27507501358', + '769.259.949-19', + '43661169739', + '646.438.057-95', + '49403410474', + '113.512.907-25', + '81322401108', + '458.825.895-23', + '92472311425', + '585.251.576-48', + '00002283074', + '796.940.600-52', + '08228682581', + '906.765.489-12', + '31437427245', + '045.504.051-45', + '21383151886', + '519.213.751-90', + '06712261581', + '884.593.736-07', + '42188048376', + ], + }); + }); }); From 22961a9c135da6f7e26b89c567b363506b54a266 Mon Sep 17 00:00:00 2001 From: Kelvin <kelvingremista2015@hotmail.com> Date: Wed, 2 Apr 2025 00:28:41 -0300 Subject: [PATCH 2/3] update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a64ca450f..d8ca90eb9 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Validator | Description **isVAT(str, countryCode)** | check if the string is a [valid VAT number][VAT Number] if validation is available for the given country code matching [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2]. <br/><br/>`countryCode` is one of `['AL', 'AR', 'AT', 'AU', 'BE', 'BG', 'BO', 'BR', 'BY', 'CA', 'CH', 'CL', 'CO', 'CR', 'CY', 'CZ', 'DE', 'DK', 'DO', 'EC', 'EE', 'EL', 'ES', 'FI', 'FR', 'GB', 'GT', 'HN', 'HR', 'HU', 'ID', 'IE', 'IL', 'IN', 'IS', 'IT', 'KZ', 'LT', 'LU', 'LV', 'MK', 'MT', 'MX', 'NG', 'NI', 'NL', 'NO', 'NZ', 'PA', 'PE', 'PH', 'PL', 'PT', 'PY', 'RO', 'RS', 'RU', 'SA', 'SE', 'SI', 'SK', 'SM', 'SV', 'TR', 'UA', 'UY', 'UZ', 'VE']`. **isWhitelisted(str, chars)** | check if the string consists only of characters that appear in the whitelist `chars`. **matches(str, pattern [, modifiers])** | check if the string matches the pattern.<br/><br/>Either `matches('foo', /foo/i)` or `matches('foo', 'foo', 'i')`. +**isCPF(str)** | check if the string follows the rules defined by the Brazilian Federal Revenue Service for CPF verification digit calculation. ## Sanitizers From ac697165da0775bc796400c7d589a7f755b8eba0 Mon Sep 17 00:00:00 2001 From: Kelvin <kelvingremista2015@hotmail.com> Date: Wed, 2 Apr 2025 23:53:22 -0300 Subject: [PATCH 3/3] feat: remove mask if exist --- src/lib/isCPF.js | 25 +++++++++++++------------ test/validators.test.js | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/lib/isCPF.js b/src/lib/isCPF.js index 23145af82..d9ddf789f 100644 --- a/src/lib/isCPF.js +++ b/src/lib/isCPF.js @@ -1,10 +1,19 @@ import assertString from './util/assertString'; +const isMask = /[.-]/g; +const repeatedDigitsRegex = /^(\d)\1{10}$/; export default function isCPF(cpf) { assertString(cpf); - if (cpf.length !== 11) return false; - if (cpf === '00000000000' || cpf === '11111111111' || cpf === '22222222222' || cpf === '33333333333' || cpf === '44444444444' || cpf === '55555555555' || cpf === '66666666666' || cpf === '77777777777' || cpf === '88888888888' || cpf === '99999999999') return false; + + let cleanedCPF = String(cpf); + + if (isMask.test(cleanedCPF)) { + cleanedCPF = cleanedCPF.replace(isMask, ''); + } + + if (cleanedCPF.length !== 11) return false; + if (repeatedDigitsRegex.test(cleanedCPF)) return false; const paramD1 = { 10: 0, 9: 1, 8: 2, 7: 3, 6: 4, 5: 5, 4: 6, 3: 7, 2: 8, @@ -12,10 +21,9 @@ export default function isCPF(cpf) { const paramD2 = { 11: 0, 10: 1, 9: 2, 8: 3, 7: 4, 6: 5, 5: 6, 4: 7, 3: 8, 2: 9, }; - let firstNineCharacters = cpf.slice(0, 9); + let firstNineCharacters = cleanedCPF.slice(0, 9); let d1 = 0; let d2 = 0; - let d1AndD2 = ''; for (let i = 10; i >= 2; i--) { if (Number.isNaN(Number(firstNineCharacters[paramD1[i]]))) return false; @@ -40,12 +48,5 @@ export default function isCPF(cpf) { d2 = 11 - (d2 % 11); } - d1AndD2 += d1; - d1AndD2 += d2; - - if (d1AndD2 === cpf.slice(cpf.length - 2)) { - return true; - } - - return false; + return cleanedCPF.slice(-2) === `${d1}${d2}`; } diff --git a/test/validators.test.js b/test/validators.test.js index 26edaba14..e8dcf2b00 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -15444,25 +15444,25 @@ describe('Validators', () => { test({ validator: 'isCPF', valid: [ - '15709954861', + '157.099.548-61', '55734947598', - '60142555835', + '601.425.558-35', '54945762139', - '36718123897', + '367.181.238-97', '43849941922', - '97989392984', + '979.893.929-84', '98273014037', - '66603094703', + '666.030.947-03', '40668005130', - '50699975565', + '506.999.755-65', '98798876465', - '81661188907', + '816.611.889-07', '52991780002', - '03703980761', + '037.039.807-61', '62281573370', - '05575125572', + '055.751.255-72', '03085770860', - '28686002129', + '286.860.021-29', '24234579793', ], invalid: [