Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement isIBAN(str) validator #1243

Merged
merged 2 commits into from
Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Validator | Description
**isBase32(str)** | check if a string is base32 encoded.
**isBase64(str)** | check if a string is base64 encoded.
**isBefore(str [, date])** | check if the string is a date that's before the specified date.
**isIBAN(str)** | check if a string is a IBAN (International Bank Account Number).
**isBIC(str)** | check if a string is a BIC (Bank Identification Code) or SWIFT code.
**isBoolean(str)** | check if a string is a boolean.
**isByteLength(str [, options])** | check if the string's length (in UTF-8 bytes) falls in a range.<br/><br/>`options` is an object which defaults to `{min:0, max: undefined}`.
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ var _isHexColor = _interopRequireDefault(require("./lib/isHexColor"));

var _isISRC = _interopRequireDefault(require("./lib/isISRC"));

var _isIBAN = _interopRequireDefault(require("./lib/isIBAN"));

var _isBIC = _interopRequireDefault(require("./lib/isBIC"));

var _isMD = _interopRequireDefault(require("./lib/isMD5"));
Expand Down Expand Up @@ -182,6 +184,7 @@ var validator = {
isIPRange: _isIPRange.default,
isFQDN: _isFQDN.default,
isBoolean: _isBoolean.default,
isIBAN: _isIBAN.default,
isBIC: _isBIC.default,
isAlpha: _isAlpha.default,
isAlphaLocales: _isAlpha.locales,
Expand Down
145 changes: 145 additions & 0 deletions lib/isIBAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = isIBAN;

var _assertString = _interopRequireDefault(require("./util/assertString"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
* List of country codes with
* corresponding IBAN regular expression
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
*/
var ibanRegexThroughCountryCode = {
AD: /^(AD[0-9]{2})\d{8}[A-Z0-9]{12}$/,
AE: /^(AE[0-9]{2})\d{3}\d{16}$/,
AL: /^(AL[0-9]{2})\d{8}[A-Z0-9]{16}$/,
AT: /^(AT[0-9]{2})\d{16}$/,
AZ: /^(AZ[0-9]{2})[A-Z0-9]{4}\d{20}$/,
BA: /^(BA[0-9]{2})\d{16}$/,
BE: /^(BE[0-9]{2})\d{12}$/,
BG: /^(BG[0-9]{2})[A-Z]{4}\d{6}[A-Z0-9]{8}$/,
BH: /^(BH[0-9]{2})[A-Z]{4}[A-Z0-9]{14}$/,
BR: /^(BR[0-9]{2})\d{23}[A-Z]{1}[A-Z0-9]{1}$/,
BY: /^(BY[0-9]{2})[A-Z0-9]{4}\d{20}$/,
CH: /^(CH[0-9]{2})\d{5}[A-Z0-9]{12}$/,
CR: /^(CR[0-9]{2})\d{18}$/,
CY: /^(CY[0-9]{2})\d{8}[A-Z0-9]{16}$/,
CZ: /^(CZ[0-9]{2})\d{20}$/,
DE: /^(DE[0-9]{2})\d{18}$/,
DK: /^(DK[0-9]{2})\d{14}$/,
DO: /^(DO[0-9]{2})[A-Z]{4}\d{20}$/,
EE: /^(EE[0-9]{2})\d{16}$/,
ES: /^(ES[0-9]{2})\d{20}$/,
FI: /^(FI[0-9]{2})\d{14}$/,
FO: /^(FO[0-9]{2})\d{14}$/,
FR: /^(FR[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
GB: /^(GB[0-9]{2})[A-Z]{4}\d{14}$/,
GE: /^(GE[0-9]{2})[A-Z0-9]{2}\d{16}$/,
GI: /^(GI[0-9]{2})[A-Z]{4}[A-Z0-9]{15}$/,
GL: /^(GL[0-9]{2})\d{14}$/,
GR: /^(GR[0-9]{2})\d{7}[A-Z0-9]{16}$/,
GT: /^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$/,
HR: /^(HR[0-9]{2})\d{17}$/,
HU: /^(HU[0-9]{2})\d{24}$/,
IE: /^(IE[0-9]{2})[A-Z0-9]{4}\d{14}$/,
IL: /^(IL[0-9]{2})\d{19}$/,
IQ: /^(IQ[0-9]{2})[A-Z]{4}\d{15}$/,
IS: /^(IS[0-9]{2})\d{22}$/,
IT: /^(IT[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
JO: /^(JO[0-9]{2})[A-Z]{4}\d{22}$/,
KW: /^(KW[0-9]{2})[A-Z]{4}[A-Z0-9]{22}$/,
KZ: /^(KZ[0-9]{2})\d{3}[A-Z0-9]{13}$/,
LB: /^(LB[0-9]{2})\d{4}[A-Z0-9]{20}$/,
LC: /^(LC[0-9]{2})[A-Z]{4}[A-Z0-9]{24}$/,
LI: /^(LI[0-9]{2})\d{5}[A-Z0-9]{12}$/,
LT: /^(LT[0-9]{2})\d{16}$/,
LU: /^(LU[0-9]{2})\d{3}[A-Z0-9]{13}$/,
LV: /^(LV[0-9]{2})[A-Z]{4}[A-Z0-9]{13}$/,
MC: /^(MC[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
MD: /^(MD[0-9]{2})[A-Z0-9]{20}$/,
ME: /^(ME[0-9]{2})\d{18}$/,
MK: /^(MK[0-9]{2})\d{3}[A-Z0-9]{10}\d{2}$/,
MR: /^(MR[0-9]{2})\d{23}$/,
MT: /^(MT[0-9]{2})[A-Z]{4}\d{5}[A-Z0-9]{18}$/,
MU: /^(MU[0-9]{2})[A-Z]{4}\d{19}[A-Z]{3}$/,
NL: /^(NL[0-9]{2})[A-Z]{4}\d{10}$/,
NO: /^(NO[0-9]{2})\d{11}$/,
PK: /^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$/,
PL: /^(PL[0-9]{2})\d{24}$/,
PS: /^(PS[0-9]{2})[A-Z0-9]{4}\d{21}$/,
PT: /^(PT[0-9]{2})\d{21}$/,
QA: /^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/,
RO: /^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$/,
RS: /^(RS[0-9]{2})\d{18}$/,
SA: /^(SA[0-9]{2})\d{2}[A-Z0-9]{18}$/,
SC: /^(SC[0-9]{2})[A-Z]{4}\d{20}[A-Z]{3}$/,
SE: /^(SE[0-9]{2})\d{20}$/,
SI: /^(SI[0-9]{2})\d{15}$/,
SK: /^(SK[0-9]{2})\d{20}$/,
SM: /^(SM[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
TL: /^(TL[0-9]{2})\d{19}$/,
TN: /^(TN[0-9]{2})\d{20}$/,
TR: /^(TR[0-9]{2})\d{5}[A-Z0-9]{17}$/,
UA: /^(UA[0-9]{2})\d{6}[A-Z0-9]{19}$/,
VA: /^(VA[0-9]{2})\d{18}$/,
VG: /^(VG[0-9]{2})[A-Z0-9]{4}\d{16}$/,
XK: /^(XK[0-9]{2})\d{16}$/
};
/**
* Check whether string has correct universal IBAN format
* The IBAN consists of up to 34 alphanumeric characters, as follows:
* Country Code using ISO 3166-1 alpha-2, two letters
* check digits, two digits and
* Basic Bank Account Number (BBAN), up to 30 alphanumeric characters.
* NOTE: Permitted IBAN characters are: digits [0-9] and the 26 latin alphabetic [A-Z]
*
* @param {string} str - string under validation
* @return {boolean}
*/

function hasValidIbanFormat(str) {
// Strip white spaces and hyphens, keep only digits and A-Z latin alphabetic
var strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase();
var isoCountryCode = strippedStr.slice(0, 2).toUpperCase();
return isoCountryCode in ibanRegexThroughCountryCode && ibanRegexThroughCountryCode[isoCountryCode].test(strippedStr);
}
/**
* Check whether string has valid IBAN Checksum
* by performing basic mod-97 operation and
* the remainder should equal 1
* -- Start by rearranging the IBAN by moving the four initial characters to the end of the string
* -- Replace each letter in the string with two digits, A -> 10, B = 11, Z = 35
* -- Interpret the string as a decimal integer and
* -- compute the remainder on division by 97 (mod 97)
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
*
* @param {string} str
* @return {boolean}
*/


function hasValidIbanChecksum(str) {
var strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase(); // Keep only digits and A-Z latin alphabetic

var rearranged = strippedStr.slice(4) + strippedStr.slice(0, 4);
var alphaCapsReplacedWithDigits = rearranged.replace(/[A-Z]/g, function (char) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make use of the ES6 features here for code succinctness if you don't mind, eg.

.replace(/[A-Z]/g, char => char.charCodeAt(0) - 55);

Same to line 133.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@profnandaa Thanks for your feedback! I'm already using ES6 features for code succinctness (i.e. arrow functions) in both of the locations you've pointed-out back in src/lib/isIBAN.js where the implementation lies, see here: https://github.com/validatorjs/validator.js/pull/1243/files#diff-ce49d7aaa9466099bb72b0307133b63dR121-R124

the above file lib/isIBAN.js is auto-generated when running tests through npm test, as the #CONTRIBUTING sections of README.md implies:

files in lib/ folder are autogenerated when running tests (npm test) and need not to be changed manually.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my bad. I reviewed the wrong part of code. You are right, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@profnandaa No problem! All is good 👍

return char.charCodeAt(0) - 55;
});
var remainder = alphaCapsReplacedWithDigits.match(/\d{1,7}/g).reduce(function (acc, value) {
return Number(acc + value) % 97;
}, '');
return remainder === 1;
}

function isIBAN(str) {
(0, _assertString.default)(str);
return hasValidIbanFormat(str) && hasValidIbanChecksum(str);
}

module.exports = exports.default;
module.exports.default = exports.default;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import isHexColor from './lib/isHexColor';

import isISRC from './lib/isISRC';

import isIBAN from './lib/isIBAN';
import isBIC from './lib/isBIC';

import isMD5 from './lib/isMD5';
Expand Down Expand Up @@ -118,6 +119,7 @@ const validator = {
isIPRange,
isFQDN,
isBoolean,
isIBAN,
isBIC,
isAlpha,
isAlphaLocales,
Expand Down
133 changes: 133 additions & 0 deletions src/lib/isIBAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import assertString from './util/assertString';

/**
* List of country codes with
* corresponding IBAN regular expression
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
*/
const ibanRegexThroughCountryCode = {
AD: /^(AD[0-9]{2})\d{8}[A-Z0-9]{12}$/,
AE: /^(AE[0-9]{2})\d{3}\d{16}$/,
AL: /^(AL[0-9]{2})\d{8}[A-Z0-9]{16}$/,
AT: /^(AT[0-9]{2})\d{16}$/,
AZ: /^(AZ[0-9]{2})[A-Z0-9]{4}\d{20}$/,
BA: /^(BA[0-9]{2})\d{16}$/,
BE: /^(BE[0-9]{2})\d{12}$/,
BG: /^(BG[0-9]{2})[A-Z]{4}\d{6}[A-Z0-9]{8}$/,
BH: /^(BH[0-9]{2})[A-Z]{4}[A-Z0-9]{14}$/,
BR: /^(BR[0-9]{2})\d{23}[A-Z]{1}[A-Z0-9]{1}$/,
BY: /^(BY[0-9]{2})[A-Z0-9]{4}\d{20}$/,
CH: /^(CH[0-9]{2})\d{5}[A-Z0-9]{12}$/,
CR: /^(CR[0-9]{2})\d{18}$/,
CY: /^(CY[0-9]{2})\d{8}[A-Z0-9]{16}$/,
CZ: /^(CZ[0-9]{2})\d{20}$/,
DE: /^(DE[0-9]{2})\d{18}$/,
DK: /^(DK[0-9]{2})\d{14}$/,
DO: /^(DO[0-9]{2})[A-Z]{4}\d{20}$/,
EE: /^(EE[0-9]{2})\d{16}$/,
ES: /^(ES[0-9]{2})\d{20}$/,
FI: /^(FI[0-9]{2})\d{14}$/,
FO: /^(FO[0-9]{2})\d{14}$/,
FR: /^(FR[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
GB: /^(GB[0-9]{2})[A-Z]{4}\d{14}$/,
GE: /^(GE[0-9]{2})[A-Z0-9]{2}\d{16}$/,
GI: /^(GI[0-9]{2})[A-Z]{4}[A-Z0-9]{15}$/,
GL: /^(GL[0-9]{2})\d{14}$/,
GR: /^(GR[0-9]{2})\d{7}[A-Z0-9]{16}$/,
GT: /^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$/,
HR: /^(HR[0-9]{2})\d{17}$/,
HU: /^(HU[0-9]{2})\d{24}$/,
IE: /^(IE[0-9]{2})[A-Z0-9]{4}\d{14}$/,
IL: /^(IL[0-9]{2})\d{19}$/,
IQ: /^(IQ[0-9]{2})[A-Z]{4}\d{15}$/,
IS: /^(IS[0-9]{2})\d{22}$/,
IT: /^(IT[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
JO: /^(JO[0-9]{2})[A-Z]{4}\d{22}$/,
KW: /^(KW[0-9]{2})[A-Z]{4}[A-Z0-9]{22}$/,
KZ: /^(KZ[0-9]{2})\d{3}[A-Z0-9]{13}$/,
LB: /^(LB[0-9]{2})\d{4}[A-Z0-9]{20}$/,
LC: /^(LC[0-9]{2})[A-Z]{4}[A-Z0-9]{24}$/,
LI: /^(LI[0-9]{2})\d{5}[A-Z0-9]{12}$/,
LT: /^(LT[0-9]{2})\d{16}$/,
LU: /^(LU[0-9]{2})\d{3}[A-Z0-9]{13}$/,
LV: /^(LV[0-9]{2})[A-Z]{4}[A-Z0-9]{13}$/,
MC: /^(MC[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/,
MD: /^(MD[0-9]{2})[A-Z0-9]{20}$/,
ME: /^(ME[0-9]{2})\d{18}$/,
MK: /^(MK[0-9]{2})\d{3}[A-Z0-9]{10}\d{2}$/,
MR: /^(MR[0-9]{2})\d{23}$/,
MT: /^(MT[0-9]{2})[A-Z]{4}\d{5}[A-Z0-9]{18}$/,
MU: /^(MU[0-9]{2})[A-Z]{4}\d{19}[A-Z]{3}$/,
NL: /^(NL[0-9]{2})[A-Z]{4}\d{10}$/,
NO: /^(NO[0-9]{2})\d{11}$/,
PK: /^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$/,
PL: /^(PL[0-9]{2})\d{24}$/,
PS: /^(PS[0-9]{2})[A-Z0-9]{4}\d{21}$/,
PT: /^(PT[0-9]{2})\d{21}$/,
QA: /^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/,
RO: /^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$/,
RS: /^(RS[0-9]{2})\d{18}$/,
SA: /^(SA[0-9]{2})\d{2}[A-Z0-9]{18}$/,
SC: /^(SC[0-9]{2})[A-Z]{4}\d{20}[A-Z]{3}$/,
SE: /^(SE[0-9]{2})\d{20}$/,
SI: /^(SI[0-9]{2})\d{15}$/,
SK: /^(SK[0-9]{2})\d{20}$/,
SM: /^(SM[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/,
TL: /^(TL[0-9]{2})\d{19}$/,
TN: /^(TN[0-9]{2})\d{20}$/,
TR: /^(TR[0-9]{2})\d{5}[A-Z0-9]{17}$/,
UA: /^(UA[0-9]{2})\d{6}[A-Z0-9]{19}$/,
VA: /^(VA[0-9]{2})\d{18}$/,
VG: /^(VG[0-9]{2})[A-Z0-9]{4}\d{16}$/,
XK: /^(XK[0-9]{2})\d{16}$/,
};

/**
* Check whether string has correct universal IBAN format
* The IBAN consists of up to 34 alphanumeric characters, as follows:
* Country Code using ISO 3166-1 alpha-2, two letters
* check digits, two digits and
* Basic Bank Account Number (BBAN), up to 30 alphanumeric characters.
* NOTE: Permitted IBAN characters are: digits [0-9] and the 26 latin alphabetic [A-Z]
*
* @param {string} str - string under validation
* @return {boolean}
*/
function hasValidIbanFormat(str) {
// Strip white spaces and hyphens, keep only digits and A-Z latin alphabetic
const strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase();
const isoCountryCode = strippedStr.slice(0, 2).toUpperCase();

return (isoCountryCode in ibanRegexThroughCountryCode) &&
ibanRegexThroughCountryCode[isoCountryCode].test(strippedStr);
}

/**
* Check whether string has valid IBAN Checksum
* by performing basic mod-97 operation and
* the remainder should equal 1
* -- Start by rearranging the IBAN by moving the four initial characters to the end of the string
* -- Replace each letter in the string with two digits, A -> 10, B = 11, Z = 35
* -- Interpret the string as a decimal integer and
* -- compute the remainder on division by 97 (mod 97)
* Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number
*
* @param {string} str
* @return {boolean}
*/
function hasValidIbanChecksum(str) {
const strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase(); // Keep only digits and A-Z latin alphabetic
const rearranged = strippedStr.slice(4) + strippedStr.slice(0, 4);
const alphaCapsReplacedWithDigits = rearranged.replace(/[A-Z]/g, char => char.charCodeAt(0) - 55);

const remainder = alphaCapsReplacedWithDigits.match(/\d{1,7}/g)
.reduce((acc, value) => Number(acc + value) % 97, '');

return remainder === 1;
}

export default function isIBAN(str) {
assertString(str);

return hasValidIbanFormat(str) && hasValidIbanChecksum(str);
}
27 changes: 27 additions & 0 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -3162,6 +3162,33 @@ describe('Validators', () => {
});
});

it('should validate IBAN', () => {
test({
validator: 'isIBAN',
valid: [
'BE71 0961 2345 6769',
'FR76 3000 6000 0112 3456 7890 189',
'DE91 1000 0000 0123 4567 89',
'GR96 0810 0010 0000 0123 4567 890',
'RO09 BCYP 0000 0012 3456 7890',
'SA44 2000 0001 2345 6789 1234',
'ES79 2100 0813 6101 2345 6789',
'CH56 0483 5012 3456 7800 9',
'GB98 MIDL 0700 9312 3456 78',
'IL170108000000012612345',
'IT60X0542811101000000123456',
'JO71CBJO0000000000001234567890',
'TR320010009999901234567890',
'BR1500000000000010932840814P2',
'LB92000700000000123123456123',
],
invalid: [
'XX22YYY1234567890123',
'FR14 2004 1010 0505 0001 3',
],
});
});

it('should validate BIC codes', () => {
test({
validator: 'isBIC',
Expand Down
Loading