From 18e14744eeebdbca8ebb4adebbedae0b7667a237 Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Fri, 11 Oct 2024 16:40:02 +1100 Subject: [PATCH 1/6] add regex validations --- index.js | 4 +- src/validations.js | 26 +++++ test/validations.js | 266 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 src/validations.js create mode 100644 test/validations.js diff --git a/index.js b/index.js index 45ee650..98f7904 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,7 @@ const sum = require('./src/sum') const sumBy = require('./src/sumBy') const uniqBy = require('./src/uniqBy') const without = require('./src/without') +const validations = require('./src/validations') module.exports = { camelize, @@ -75,5 +76,6 @@ module.exports = { sum, sumBy, uniqBy, - without + without, + validations } diff --git a/src/validations.js b/src/validations.js new file mode 100644 index 0000000..330c519 --- /dev/null +++ b/src/validations.js @@ -0,0 +1,26 @@ +/* eslint-disable no-misleading-character-class */ +'use strict' +// https://v8.dev/features/regexp-v-flag +// https://mathiasbynens.be/notes/es-unicode-property-escapes +// https://www.regular-expressions.info/unicode.html + +const CHARACTERS = '\\p{L}\\p{M}' +const INPUT_BASE_REG_EXP = new RegExp(`-0-9.,;:<>?/\n${CHARACTERS}'_ &()/+@%`, 'u') +const DIGITS = '0-9' + +const REGEX_VALIDATION = { + NUMBER: new RegExp(`^[${DIGITS}]+$`), + EMAIL: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/, + PATH: /([^/]*)\/*$/, + NAME: new RegExp(`^[-${CHARACTERS}' &()/]+$`, 'u'), + NAME_WITH_DIGITS: new RegExp(`^[-${CHARACTERS}${DIGITS}' &()/]+$`, 'u'), + INPUT: new RegExp(`^[${INPUT_BASE_REG_EXP.source}]+$`, 'u'), + ADDRESS: new RegExp(`^[${INPUT_BASE_REG_EXP.source}#]+$`, 'u'), + PHONE_CODE: /^(\+)*[0-9]+$/, + IMAGE: /^data:image\/[A-Za-z+]+;base64,[A-Za-z0-9+/=]+$/, + FILE: /^data:(image|application|video)\/[A-Za-z0-9+]+;base64,[A-Za-z0-9+/=]+$/, + FILENAME: new RegExp(`^[-${DIGITS}.${CHARACTERS} ()_]+$`, 'u'), + ADMIN_PASSWORD: /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/ +} + +module.exports = REGEX_VALIDATION diff --git a/test/validations.js b/test/validations.js new file mode 100644 index 0000000..1cdc61c --- /dev/null +++ b/test/validations.js @@ -0,0 +1,266 @@ +'use strict' + +/* eslint-env mocha */ + +const assert = require('assert') +const { validations } = require('../index') + +const { + NUMBER, + EMAIL, + PATH, + NAME, + NAME_WITH_DIGITS, + INPUT, + ADDRESS, + PHONE_CODE, + IMAGE, + FILE, + FILENAME, + ADMIN_PASSWORD +} = validations + +describe('validations', () => { + it('NUMBER, validate numbers', () => { + assert.strictEqual(NUMBER.test('100'), true) + + assert.strictEqual(NUMBER.test('-100'), false) + assert.strictEqual(NUMBER.test('1.2'), false) + assert.strictEqual(NUMBER.test('.2'), false) + assert.strictEqual(NUMBER.test('-0'), false) + assert.strictEqual(NUMBER.test('string'), false) + }) + + it('EMAIL, validate email addresses', () => { + assert.strictEqual(EMAIL.test('test5678@email.com'), true) + assert.strictEqual(EMAIL.test('test5678test@email.com'), true) + assert.strictEqual(EMAIL.test('test5678@email123.com'), true) + assert.strictEqual(EMAIL.test('test5678@email123.io'), true) + assert.strictEqual(EMAIL.test('test5678@email123.io123'), true) + + assert.strictEqual(NUMBER.test('abc'), false) + assert.strictEqual(NUMBER.test('5678test@email.com'), false) + assert.strictEqual(NUMBER.test('test@123email.com'), false) + assert.strictEqual(NUMBER.test('test@123email.324'), false) + }) + + it('PATH, validate path, url', () => { + assert.strictEqual(PATH.test('/'), true) + assert.strictEqual(PATH.test('/path'), true) + assert.strictEqual(PATH.test('path/'), true) + assert.strictEqual(PATH.test('/path/test'), true) + assert.strictEqual(PATH.test('//'), true) + assert.strictEqual(PATH.test('/'), true) + assert.strictEqual(PATH.test('a/b/c/d/e/f/g/c/d/f/k/d/'), true) + assert.strictEqual(PATH.test('#'), true) + assert.strictEqual(PATH.test('/3'), true) + + assert.strictEqual(PATH.test(''), true) + assert.strictEqual(PATH.test('//abc'), true) + }) + + it('NAME, validate NAME', () => { + assert.strictEqual(NAME.test('こんにちは'), true) // Japanese + assert.strictEqual(NAME.test('你好'), true) // Chinese + assert.strictEqual(NAME.test('Здравствуйте'), true) // Russian + + // Name with digits + assert.strictEqual(NAME.test('こんにちは3'), false) // Japanese + assert.strictEqual(NAME.test('你好4'), false) // Chinese + assert.strictEqual(NAME.test('Здравствуйте4'), false) // Russian + + assert.strictEqual(NAME.test('*'), false) // special characters + assert.strictEqual(NAME.test('😊'), false) // emoji + }) + + it('INPUT, validate input text', () => { + // input with/without digits + assert.strictEqual(INPUT.test('こんにちは'), true) // Japanese + assert.strictEqual(INPUT.test('你好'), true) // Chinese + assert.strictEqual(INPUT.test('Здравствуйте'), true) // Russian + + assert.strictEqual(INPUT.test('こんにちは3'), true) // Japanese + assert.strictEqual(INPUT.test('你好4'), true) // Chinese + assert.strictEqual(INPUT.test('Здравствуйте4'), true) // Russian + + assert.strictEqual(INPUT.test('*'), false) // special characters + assert.strictEqual(INPUT.test('😊'), false) // emoji + }) + + it('NAME_WITH_DIGITS, validate name with digits', () => { + assert.strictEqual(NAME_WITH_DIGITS.test('こんにちは3'), true) // Japanese + assert.strictEqual(NAME_WITH_DIGITS.test('你好4'), true) // Chinese + assert.strictEqual(NAME_WITH_DIGITS.test('Здравствуйте4'), true) // Russian + + // without digits + assert.strictEqual(NAME_WITH_DIGITS.test('こんにちは'), true) // Japanese + assert.strictEqual(NAME_WITH_DIGITS.test('你好'), true) // Chinese + assert.strictEqual(NAME_WITH_DIGITS.test('Здравствуйте'), true) // Russian + + assert.strictEqual(NAME_WITH_DIGITS.test('*'), false) // special characters + assert.strictEqual(NAME_WITH_DIGITS.test('😊'), false) // emoji + }) + + it('ADDRESS, validate address text', () => { + assert.strictEqual(ADDRESS.test('123 Main Street, Springfield, IL 62701, USA'), true) // english + assert.strictEqual(ADDRESS.test('東京都新宿区西新宿2丁目8-1'), true) // Japanese + assert.strictEqual(ADDRESS.test('北京市朝阳区建国门外大街甲12'), true) // Chinese + assert.strictEqual(ADDRESS.test('г. Москва, ул. Тверская, д. 15'), true) // Russian + assert.strictEqual(ADDRESS.test('м. Київ, вул. Хрещатик, 22'), true) // Ukrainian + + // Simple addresses + assert.strictEqual(ADDRESS.test('123 Main St.'), true) + assert.strictEqual(ADDRESS.test('456 Elm Street; Apt #2'), true) + assert.strictEqual(ADDRESS.test('789 Maple Ave, Unit 5'), true) + assert.strictEqual(ADDRESS.test('321 Broadway St: Suite 100'), true) + + // punctuation in address + assert.strictEqual(ADDRESS.test('15-20 First Ave.'), true) + assert.strictEqual(ADDRESS.test('30/40 Oak Drive'), true) + assert.strictEqual(ADDRESS.test('25:40 Second St.'), true) + assert.strictEqual(ADDRESS.test('47:50 3rd Blvd; Apt 4B'), true) + + // Including special character + assert.strictEqual(ADDRESS.test('Street Name & Co.'), true) + assert.strictEqual(ADDRESS.test('First St. + Second St.'), true) + assert.strictEqual(ADDRESS.test('Apt #123 & 456'), true) + assert.strictEqual(ADDRESS.test('Cross St @ 78'), true) + + // Address with new line + assert.strictEqual(ADDRESS.test('123 Main St\nApt 4'), true) + assert.strictEqual(ADDRESS.test('456 Elm St\nSuite B'), true) + + // Complex address + assert.strictEqual(ADDRESS.test('221B Baker Street, London'), true) + assert.strictEqual(ADDRESS.test('1234 Example Rd, City, State 56789'), true) + assert.strictEqual(ADDRESS.test('PO Box 1234'), true) + assert.strictEqual(ADDRESS.test('456 High St. #10, New York, NY 10001'), true) + assert.strictEqual(ADDRESS.test('456 Elm St@'), true) // special character + + // Leading/trailing spaces + assert.strictEqual(ADDRESS.test(' 123 Main St '), true) // leading/trailing space + assert.strictEqual(ADDRESS.test('#456 Elm St'), true) // leading character + assert.strictEqual(ADDRESS.test('Unit 5; Apt #'), true) // leading character + assert.strictEqual(ADDRESS.test('22 Main St? Apt'), true) // question mark + assert.strictEqual(ADDRESS.test('\n'), true) // newline + + // Invalid characters + assert.strictEqual(ADDRESS.test('123 Main St!'), false) // Exclamation mark + assert.strictEqual(ADDRESS.test('789 Maple Ave#1*'), false) // asterisk + + assert.strictEqual(ADDRESS.test(''), false) // (empty) + + // // invalid characters + assert.strictEqual(ADDRESS.test('!!!'), false) + // assert.strictEqual(ADDRESS.test('@@@'), false) + assert.strictEqual(ADDRESS.test('123*456'), false) // contains asterisk + + // // improper address format + assert.strictEqual(ADDRESS.test('First St $ Apt 4'), false) // dollar sign + + assert.strictEqual(ADDRESS.test('*'), false) // special characters + assert.strictEqual(ADDRESS.test('😊'), false) // emoji + }) + + it('PHONE_CODE, validate phone code', () => { + assert.strictEqual(PHONE_CODE.test('+123'), true) // single plus sign + assert.strictEqual(PHONE_CODE.test('++456'), true) // multiple plus sign + assert.strictEqual(PHONE_CODE.test('789'), true) // no plus sign + assert.strictEqual(PHONE_CODE.test('+1'), true) // plus with digit + assert.strictEqual(PHONE_CODE.test('+123456'), true) // plus with multiple digit + assert.strictEqual(PHONE_CODE.test('0'), true) // only digit + assert.strictEqual(PHONE_CODE.test('+0'), true) // leading plus sign with zero + + assert.strictEqual(PHONE_CODE.test('+12a34'), false) // plus sign followed by non-digts + assert.strictEqual(PHONE_CODE.test('a123'), false) // leading character + assert.strictEqual(PHONE_CODE.test('++'), false) // double plus sign + assert.strictEqual(PHONE_CODE.test(''), false) // (empty) + assert.strictEqual(PHONE_CODE.test('+ 123'), false) // spaces around number + assert.strictEqual(PHONE_CODE.test('+123abc'), false) // invalid characters mixed + assert.strictEqual(PHONE_CODE.test('-123'), false) // negative sign + assert.strictEqual(PHONE_CODE.test('*'), false) // special characters + assert.strictEqual(PHONE_CODE.test('😊'), false) // emoji + }) + + it('IMAGE, validate image uri', () => { + assert.strictEqual(IMAGE.test('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // image/png + assert.strictEqual(IMAGE.test('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIu'), true) // image/jpeg + assert.strictEqual(IMAGE.test('data:image/gif;base64,R0lGODlhEAAQAMIAAAAAAP///wAAACH5BAEAAAAALAAAAAAQABAAAAIhIhIhIhIhIhIhIhIhIh'), true) // image/gif + assert.strictEqual(IMAGE.test('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/svg + assert.strictEqual(IMAGE.test('data:image/bmp;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/bmp + assert.strictEqual(IMAGE.test('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // allow all types of format + + assert.strictEqual(IMAGE.test('image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing data:image/ + assert.strictEqual(IMAGE.test('data:image/png,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // ;base64, + assert.strictEqual(IMAGE.test('data:image/png;base64,iVBORw0KGgo@@@AAAAUA'), false) // incorrect base64 + assert.strictEqual(IMAGE.test('data:image/png;base64,'), false) // incorrect string, no base64 + assert.strictEqual(IMAGE.test('data:image/png;base64,abcde==!#%&'), false) // Missing base64 + }) + + it('FILE, validate data uri', () => { + assert.strictEqual(FILE.test('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // image/png + assert.strictEqual(FILE.test('data:application/pdf;base64,JVBERi0xLjQKJcfs'), true) // image/pdf + assert.strictEqual(FILE.test('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU'), true) // video/mp4 + assert.strictEqual(FILE.test('data:video/webm;base64,GkXFo3zqEKZxKbOQGh'), true) // video/webm + assert.strictEqual(FILE.test('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/svg + assert.strictEqual(FILE.test('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkL'), true) // image/jpeg + assert.strictEqual(FILE.test('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // allow all types of format + + assert.strictEqual(FILE.test('data:image;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing media type + assert.strictEqual(FILE.test('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='), false) // Invalid media type + assert.strictEqual(FILE.test('data:application/json;base64,{"key":"value"}'), false) // Invalid media type + assert.strictEqual(FILE.test('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU...'), false) // Correct type but incomplete format + assert.strictEqual(FILE.test('data:image/png;base64 iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing the comma + assert.strictEqual(FILE.test('data://image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Incorrect syntax + assert.strictEqual(FILE.test('data:image/png;base64,'), false) // No Base64 data + assert.strictEqual(FILE.test('data:image/png;base64,abcde==!'), false) // Invalid characters + assert.strictEqual(FILE.test('data:image/jpeg;base64,abc@123'), false) // Invalid characters in Base64 + }) + + it('FILENAME, validate file name', () => { + assert.strictEqual(FILENAME.test('valid-file.txt'), true) + assert.strictEqual(FILENAME.test('another_valid_file123'), true) + assert.strictEqual(FILENAME.test('file_with_spaces (and more).jpg'), true) + assert.strictEqual(FILENAME.test('file-name_1234.pdf'), true) + assert.strictEqual(FILENAME.test(' '), true) // Space should be valid + assert.strictEqual(FILENAME.test('...'), true) // Dots should be valid + assert.strictEqual(FILENAME.test('-_'), true) // Dash and underscore should be valid + + // Special chars + assert.strictEqual(FILENAME.test('file@name_with_special&chars.txt'), false) + assert.strictEqual(FILENAME.test('file.name;with,commas:andthan>symbols'), false) + assert.strictEqual(FILENAME.test('file_with_special_chars&%'), false) + + assert.strictEqual(FILENAME.test('invalid/file.txt'), false) // Contains / + assert.strictEqual(FILENAME.test('invalid\\file.txt'), false) // Contains \ + assert.strictEqual(FILENAME.test('invalid:file.txt'), false) // Contains : + assert.strictEqual(FILENAME.test('invalid*file.txt'), false) // Contains * + assert.strictEqual(FILENAME.test('invalid|file.txt'), false) // Contains | + assert.strictEqual(FILENAME.test('invalidfile.txt'), false) // Contains > + assert.strictEqual(FILENAME.test(''), false) // Empty string + assert.strictEqual(FILENAME.test('invalid?file.txt'), false) // Contains ? + }) + + it('ADMIN_PASSWORD, validate password', () => { + // valid admin password + assert.strictEqual(ADMIN_PASSWORD.test('A1!strongpass'), true) + assert.strictEqual(ADMIN_PASSWORD.test('Secure#Password1'), true) + assert.strictEqual(ADMIN_PASSWORD.test('Another@Valid3Password'), true) + assert.strictEqual(ADMIN_PASSWORD.test('Passw0rd!'), true) + assert.strictEqual(ADMIN_PASSWORD.test('1234Abc!@#$'), true) + assert.strictEqual(ADMIN_PASSWORD.test('Valid1Password&'), true) + assert.strictEqual(ADMIN_PASSWORD.test('MyP@ssw0rd!'), true) + assert.strictEqual(ADMIN_PASSWORD.test('Uppercase1!'), true) // No lowercase letters + + assert.strictEqual(ADMIN_PASSWORD.test('short1!'), false) // Too short + assert.strictEqual(ADMIN_PASSWORD.test('nospecialchars'), false) // No special characters + assert.strictEqual(ADMIN_PASSWORD.test('NOLOWERCASE1!'), false) // No lowercase letters + assert.strictEqual(ADMIN_PASSWORD.test('nouppercase1!'), false) // No uppercase letters + assert.strictEqual(ADMIN_PASSWORD.test('NoDigits!'), false) // No digits + assert.strictEqual(ADMIN_PASSWORD.test('NoSpecialChars1'), false) // No special characters + assert.strictEqual(ADMIN_PASSWORD.test('lowercase1!'), false) // No uppercase letters + assert.strictEqual(ADMIN_PASSWORD.test('12345678!'), false) // No letters + assert.strictEqual(ADMIN_PASSWORD.test('Password!'), false) // No digits + }) +}) From 81b2625df019d18a37f6660c3e8d90e7ab8d96e6 Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Fri, 11 Oct 2024 17:17:25 +1100 Subject: [PATCH 2/6] update per pr checklist --- CHANGELOG | 3 +++ README.md | 1 + index.d.ts | 3 ++- index.js | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 67f8188..8726a94 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +#1.16.0 +- feat: regex validations + #1.15.0 - feat: snakeCase - feat: promiseResolveCb diff --git a/README.md b/README.md index a5b4fe1..ff78a23 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,4 @@ Currently supported utils: - `sumBy` - calculate sum of array items using iteratee function or string shortcut - `uniqBy` - get unique values of array by the iteratee function or property path - `without` - creates an array of values from the first argument excluding all given arguments +- `[ NUMBER, EMAIL, PATH, NAME, NAME_WITH_DIGITS, INPUT, ADDRESS, PHONE_CODE, IMAGE, FILE, FILENAME, ADMIN_PASSWORD ]` - list of regex validations diff --git a/index.d.ts b/index.d.ts index c8bb38d..0b3224d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,4 +34,5 @@ export function snakeCase (str: string): string export function sum (values: Array): number export function sumBy (values: Array, iteratee: Function | string): number export function uniqBy (array: Array, iteratee: Function | string): Array -export function without (array: Array, ...values: Array): Array +export const validation : { NUMBER: RegExp; EMAIL: RegExp ; PATH: RegExp; NAME: RegExp; NAME_WITH_DIGITS: RegExp; INPUT: RegExp; ADDRESS: RegExp; PHONE_CODE: RegExp; IMAGE: RegExp; FILE: RegExp; FILENAME: RegExp; ADMIN_PASSWORD: RegExp } +export function without (array: Array, ...values: Array): Array \ No newline at end of file diff --git a/index.js b/index.js index 98f7904..c71c20b 100644 --- a/index.js +++ b/index.js @@ -76,6 +76,6 @@ module.exports = { sum, sumBy, uniqBy, - without, - validations + validations, + without } diff --git a/package.json b/package.json index b45f0d3..0a9b1f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitfinexcom/lib-js-util-base", - "version": "1.15.0", + "version": "1.16.0", "description": "general utils", "main": "index.js", "scripts": { From 7b426efe418693bf429b905753ed7068beb8f884 Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Fri, 11 Oct 2024 17:43:24 +1100 Subject: [PATCH 3/6] update per alphabetical ordering --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c71c20b..6fda176 100644 --- a/index.js +++ b/index.js @@ -36,8 +36,8 @@ const snakeCase = require('./src/snakeCase') const sum = require('./src/sum') const sumBy = require('./src/sumBy') const uniqBy = require('./src/uniqBy') +const validation = require('./src/validations') const without = require('./src/without') -const validations = require('./src/validations') module.exports = { camelize, @@ -76,6 +76,6 @@ module.exports = { sum, sumBy, uniqBy, - validations, + validation, without } From d61151bcb4b5879041bef125ccae3e0536f75f3b Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Wed, 16 Oct 2024 17:43:32 +1100 Subject: [PATCH 4/6] add pr feedback --- index.d.ts | 4 +- index.js | 4 +- src/validateInput.js | 49 ++++++++ src/validations.js | 26 ----- test/validateInput.js | 254 ++++++++++++++++++++++++++++++++++++++++ test/validations.js | 266 ------------------------------------------ 6 files changed, 307 insertions(+), 296 deletions(-) create mode 100644 src/validateInput.js delete mode 100644 src/validations.js create mode 100644 test/validateInput.js delete mode 100644 test/validations.js diff --git a/index.d.ts b/index.d.ts index 0b3224d..a38edc5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,5 +34,5 @@ export function snakeCase (str: string): string export function sum (values: Array): number export function sumBy (values: Array, iteratee: Function | string): number export function uniqBy (array: Array, iteratee: Function | string): Array -export const validation : { NUMBER: RegExp; EMAIL: RegExp ; PATH: RegExp; NAME: RegExp; NAME_WITH_DIGITS: RegExp; INPUT: RegExp; ADDRESS: RegExp; PHONE_CODE: RegExp; IMAGE: RegExp; FILE: RegExp; FILENAME: RegExp; ADMIN_PASSWORD: RegExp } -export function without (array: Array, ...values: Array): Array \ No newline at end of file +export function validateInput ( input: string, format: string ): Boolean +export function without (array: Array, ...values: Array): Array diff --git a/index.js b/index.js index 6fda176..d349bc7 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ const snakeCase = require('./src/snakeCase') const sum = require('./src/sum') const sumBy = require('./src/sumBy') const uniqBy = require('./src/uniqBy') -const validation = require('./src/validations') +const validateInput = require('./src/validateInput') const without = require('./src/without') module.exports = { @@ -76,6 +76,6 @@ module.exports = { sum, sumBy, uniqBy, - validation, + validateInput, without } diff --git a/src/validateInput.js b/src/validateInput.js new file mode 100644 index 0000000..0dbcace --- /dev/null +++ b/src/validateInput.js @@ -0,0 +1,49 @@ +/* eslint-disable no-misleading-character-class */ +'use strict' +// https://v8.dev/features/regexp-v-flag +// https://mathiasbynens.be/notes/es-unicode-property-escapes +// https://www.regular-expressions.info/unicode.html + +const CHARACTERS = '\\p{L}\\p{M}' +const INPUT_BASE_REG_EXP = new RegExp(`-0-9.,;:<>?/\n${CHARACTERS}'_ &()/+@%`, 'u') +const DIGITS = '0-9' + +const NUMBER = /^-?\d*[\.]?\d+$/ +const EMAIL = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i +const PATH = /([^/]*)\/*$/ +const NAME = new RegExp(`^[-${CHARACTERS}' &()/]+$`, 'u') +const NAME_WITH_DIGITS = new RegExp(`^[-${CHARACTERS}${DIGITS}' &()/]+$`, 'u') +const INPUT = new RegExp(`^[${INPUT_BASE_REG_EXP.source}]+$`, 'u') +const ADDRESS = new RegExp(`^[${INPUT_BASE_REG_EXP.source}#]+$`, 'u') +const PHONE_CODE = /^(\+?\d{1,3}|\d{1,4})$/ +const IMAGE = /^data:image\/[A-Za-z+]+;base64,[A-Za-z0-9+/=]+$/ +const FILE = /^data:(image|application|video)\/[A-Za-z0-9+]+;base64,[A-Za-z0-9+/=]+$/ +const FILENAME = new RegExp(`^[-${DIGITS}.${CHARACTERS} ()_]+$`, 'u') +const PASSWORD = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/ + +/** + * @param {string} input + * @param {'NAME'|'EMAIL'|..} format + * @returns {boolean} + * @throws {Error} + */ +const validateInput = (input, format) => { + switch (format) { + case 'NAME': return NAME.test(input) + case 'NAME_WITH_DIGITS': return NAME_WITH_DIGITS.test(input) + case 'NUMBER': return NUMBER.test(input) + case 'EMAIL': return EMAIL.test(input) + case 'PATH': return PATH.test(input) + case 'INPUT': return INPUT.test(input) + case 'ADDRESS': return ADDRESS.test(input) + case 'PHONE_CODE': return PHONE_CODE.test(input) + case 'IMAGE': return IMAGE.test(input) + case 'FILE': return FILE.test(input) + case 'FILENAME': return FILENAME.test(input) + case 'PASSWORD': return PASSWORD.test(input) + + default: throw new Error('ERR_FORMAT_NOT_SUPPORTED') + } +} + +module.exports = validateInput diff --git a/src/validations.js b/src/validations.js deleted file mode 100644 index 330c519..0000000 --- a/src/validations.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable no-misleading-character-class */ -'use strict' -// https://v8.dev/features/regexp-v-flag -// https://mathiasbynens.be/notes/es-unicode-property-escapes -// https://www.regular-expressions.info/unicode.html - -const CHARACTERS = '\\p{L}\\p{M}' -const INPUT_BASE_REG_EXP = new RegExp(`-0-9.,;:<>?/\n${CHARACTERS}'_ &()/+@%`, 'u') -const DIGITS = '0-9' - -const REGEX_VALIDATION = { - NUMBER: new RegExp(`^[${DIGITS}]+$`), - EMAIL: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/, - PATH: /([^/]*)\/*$/, - NAME: new RegExp(`^[-${CHARACTERS}' &()/]+$`, 'u'), - NAME_WITH_DIGITS: new RegExp(`^[-${CHARACTERS}${DIGITS}' &()/]+$`, 'u'), - INPUT: new RegExp(`^[${INPUT_BASE_REG_EXP.source}]+$`, 'u'), - ADDRESS: new RegExp(`^[${INPUT_BASE_REG_EXP.source}#]+$`, 'u'), - PHONE_CODE: /^(\+)*[0-9]+$/, - IMAGE: /^data:image\/[A-Za-z+]+;base64,[A-Za-z0-9+/=]+$/, - FILE: /^data:(image|application|video)\/[A-Za-z0-9+]+;base64,[A-Za-z0-9+/=]+$/, - FILENAME: new RegExp(`^[-${DIGITS}.${CHARACTERS} ()_]+$`, 'u'), - ADMIN_PASSWORD: /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/ -} - -module.exports = REGEX_VALIDATION diff --git a/test/validateInput.js b/test/validateInput.js new file mode 100644 index 0000000..3de3314 --- /dev/null +++ b/test/validateInput.js @@ -0,0 +1,254 @@ +'use strict' + +/* eslint-env mocha */ + +const assert = require('assert') +const { validateInput } = require('../index') + +describe('validations', () => { + it('NUMBER, validate numbers', () => { + assert.strictEqual(validateInput('100', 'NUMBER'), true) + + assert.strictEqual(validateInput('-100', 'NUMBER'), true) + assert.strictEqual(validateInput('1.2', 'NUMBER'), true) + assert.strictEqual(validateInput('.2', 'NUMBER'), true) + assert.strictEqual(validateInput('-0', 'NUMBER'), true) + assert.strictEqual(validateInput('0.000023323232323', 'NUMBER'), true) + + assert.strictEqual(validateInput('-0.000023323232323', 'NUMBER'), true) + assert.strictEqual(validateInput('string', 'NUMBER'), false) + }) + + it('EMAIL, validate email addresses', () => { + assert.strictEqual(validateInput('test5678@email.com', 'EMAIL'), true) + assert.strictEqual(validateInput('test5678test@email.com', 'EMAIL'), true) + assert.strictEqual(validateInput('test5678@email123.com', 'EMAIL'), true) + assert.strictEqual(validateInput('test5678@email123.io', 'EMAIL'), true) + assert.strictEqual(validateInput('test5678@email123.io123', 'EMAIL'), true) + assert.strictEqual(validateInput('5678test@email.com', 'EMAIL'), true) + assert.strictEqual(validateInput('test@123email.com', 'EMAIL'), true) + assert.strictEqual(validateInput('test@123email.324', 'EMAIL'), true) + + assert.strictEqual(validateInput('abc', 'EMAIL'), false) + }) + + it('PATH, validate path, url', () => { + assert.strictEqual(validateInput('/', 'PATH'), true) + assert.strictEqual(validateInput('/path', 'PATH'), true) + assert.strictEqual(validateInput('path/', 'PATH'), true) + assert.strictEqual(validateInput('/path/test', 'PATH'), true) + assert.strictEqual(validateInput('//', 'PATH'), true) + assert.strictEqual(validateInput('/', 'PATH'), true) + assert.strictEqual(validateInput('a/b/c/d/e/f/g/c/d/f/k/d/', 'PATH'), true) + assert.strictEqual(validateInput('#', 'PATH'), true) + assert.strictEqual(validateInput('/3', 'PATH'), true) + + assert.strictEqual(validateInput('', 'PATH'), true) + assert.strictEqual(validateInput('//abc', 'PATH'), true) + }) + + it('NAME, validate NAME', () => { + assert.strictEqual(validateInput('こんにちは', 'NAME'), true) // Japanese + assert.strictEqual(validateInput('你好', 'NAME'), true) // Chinese + assert.strictEqual(validateInput('Здравствуйте', 'NAME'), true) // Russian + + // Name with digits + assert.strictEqual(validateInput('こんにちは3', 'NAME'), false) // Japanese + assert.strictEqual(validateInput('你好4', 'NAME'), false) // Chinese + assert.strictEqual(validateInput('Здравствуйте4', 'NAME'), false) // Russian + + assert.strictEqual(validateInput('*', 'NAME'), false) // special characters + assert.strictEqual(validateInput('😊', 'NAME'), false) // emoji + }) + + it('INPUT, validate input text', () => { + // input with/without digits + assert.strictEqual(validateInput('こんにちは', 'INPUT'), true) // Japanese + assert.strictEqual(validateInput('你好', 'INPUT'), true) // Chinese + assert.strictEqual(validateInput('Здравствуйте', 'INPUT'), true) // Russian + + assert.strictEqual(validateInput('こんにちは3', 'INPUT'), true) // Japanese + assert.strictEqual(validateInput('你好4', 'INPUT'), true) // Chinese + assert.strictEqual(validateInput('Здравствуйте4', 'INPUT'), true) // Russian + + assert.strictEqual(validateInput('*', 'INPUT'), false) // special characters + assert.strictEqual(validateInput('😊', 'INPUT'), false) // emoji + }) + + it('NAME_WITH_DIGITS, validate name with digits', () => { + assert.strictEqual(validateInput('こんにちは3', 'NAME_WITH_DIGITS'), true) // Japanese + assert.strictEqual(validateInput('你好4', 'NAME_WITH_DIGITS'), true) // Chinese + assert.strictEqual(validateInput('Здравствуйте4', 'NAME_WITH_DIGITS'), true) // Russian + + // without digits + assert.strictEqual(validateInput('こんにちは', 'NAME_WITH_DIGITS'), true) // Japanese + assert.strictEqual(validateInput('你好', 'NAME_WITH_DIGITS'), true) // Chinese + assert.strictEqual(validateInput('Здравствуйте', 'NAME_WITH_DIGITS'), true) // Russian + + assert.strictEqual(validateInput('*', 'NAME_WITH_DIGITS'), false) // special characters + assert.strictEqual(validateInput('😊', 'NAME_WITH_DIGITS'), false) // emoji + }) + + it('ADDRESS, validate address text', () => { + assert.strictEqual(validateInput('123 Main Street, Springfield, IL 62701, USA', 'ADDRESS'), true) // english + assert.strictEqual(validateInput('東京都新宿区西新宿2丁目8-1', 'ADDRESS'), true) // Japanese + assert.strictEqual(validateInput('北京市朝阳区建国门外大街甲12', 'ADDRESS'), true) // Chinese + assert.strictEqual(validateInput('г. Москва, ул. Тверская, д. 15', 'ADDRESS'), true) // Russian + assert.strictEqual(validateInput('м. Київ, вул. Хрещатик, 22', 'ADDRESS'), true) // Ukrainian + + // Simple addresses + assert.strictEqual(validateInput('123 Main St.', 'ADDRESS'), true) + assert.strictEqual(validateInput('456 Elm Street; Apt #2', 'ADDRESS'), true) + assert.strictEqual(validateInput('789 Maple Ave, Unit 5', 'ADDRESS'), true) + assert.strictEqual(validateInput('321 Broadway St: Suite 100', 'ADDRESS'), true) + + // punctuation in address + assert.strictEqual(validateInput('15-20 First Ave.', 'ADDRESS'), true) + assert.strictEqual(validateInput('30/40 Oak Drive', 'ADDRESS'), true) + assert.strictEqual(validateInput('25:40 Second St.', 'ADDRESS'), true) + assert.strictEqual(validateInput('47:50 3rd Blvd; Apt 4B', 'ADDRESS'), true) + + // Including special character + assert.strictEqual(validateInput('Street Name & Co.', 'ADDRESS'), true) + assert.strictEqual(validateInput('First St. + Second St.', 'ADDRESS'), true) + assert.strictEqual(validateInput('Apt #123 & 456', 'ADDRESS'), true) + assert.strictEqual(validateInput('Cross St @ 78', 'ADDRESS'), true) + + // Address with new line + assert.strictEqual(validateInput('123 Main St\nApt 4', 'ADDRESS'), true) + assert.strictEqual(validateInput('456 Elm St\nSuite B', 'ADDRESS'), true) + + // Complex address + assert.strictEqual(validateInput('221B Baker Street, London', 'ADDRESS'), true) + assert.strictEqual(validateInput('1234 Example Rd, City, State 56789', 'ADDRESS'), true) + assert.strictEqual(validateInput('PO Box 1234', 'ADDRESS'), true) + assert.strictEqual(validateInput('456 High St. #10, New York, NY 10001', 'ADDRESS'), true) + assert.strictEqual(validateInput('456 Elm St@', 'ADDRESS'), true) // special character + + // Leading/trailing spaces + assert.strictEqual(validateInput(' 123 Main St ', 'ADDRESS'), true) // leading/trailing space + assert.strictEqual(validateInput('#456 Elm St', 'ADDRESS'), true) // leading character + assert.strictEqual(validateInput('Unit 5; Apt #', 'ADDRESS'), true) // leading character + assert.strictEqual(validateInput('22 Main St? Apt', 'ADDRESS'), true) // question mark + assert.strictEqual(validateInput('\n', 'ADDRESS'), true) // newline + + // Invalid characters + assert.strictEqual(validateInput('123 Main St!', 'ADDRESS'), false) // Exclamation mark + assert.strictEqual(validateInput('789 Maple Ave#1*', 'ADDRESS'), false) // asterisk + + assert.strictEqual(validateInput('', 'ADDRESS'), false) // (empty) + + // // invalid characters + assert.strictEqual(validateInput('!!!', 'ADDRESS'), false) + // assert.strictEqual(validateInput('@@@', 'ADDRESS'), false) + assert.strictEqual(validateInput('123*456', 'ADDRESS'), false) // contains asterisk + + // // improper address format + assert.strictEqual(validateInput('First St $ Apt 4', 'ADDRESS'), false) // dollar sign + + assert.strictEqual(validateInput('*', 'ADDRESS'), false) // special characters + assert.strictEqual(validateInput('😊', 'ADDRESS'), false) // emoji + }) + + it('PHONE_CODE, validate phone code', () => { + assert.strictEqual(validateInput('+123', 'PHONE_CODE'), true) // single plus sign + assert.strictEqual(validateInput('789', 'PHONE_CODE'), true) // no plus sign + assert.strictEqual(validateInput('+1', 'PHONE_CODE'), true) // plus with digit + assert.strictEqual(validateInput('0', 'PHONE_CODE'), true) // only digit + assert.strictEqual(validateInput('+0', 'PHONE_CODE'), true) // leading plus sign with zero + + assert.strictEqual(validateInput('+123456', 'PHONE_CODE'), false) // plus with multiple digits, more than 4 + assert.strictEqual(validateInput('++456', 'PHONE_CODE'), false) // multiple plus sign + assert.strictEqual(validateInput('+12a34', 'PHONE_CODE'), false) // plus sign followed by non-digts + assert.strictEqual(validateInput('a123', 'PHONE_CODE'), false) // leading character + assert.strictEqual(validateInput('++', 'PHONE_CODE'), false) // double plus sign + assert.strictEqual(validateInput('', 'PHONE_CODE'), false) // (empty) + assert.strictEqual(validateInput('+ 123', 'PHONE_CODE'), false) // spaces around number + assert.strictEqual(validateInput('+123abc', 'PHONE_CODE'), false) // invalid characters mixed + assert.strictEqual(validateInput('-123', 'PHONE_CODE'), false) // negative sign + assert.strictEqual(validateInput('*', 'PHONE_CODE'), false) // special characters + assert.strictEqual(validateInput('😊', 'PHONE_CODE'), false) // emoji + }) + + it('IMAGE, validate image uri', () => { + assert.strictEqual(validateInput('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'IMAGE'), true) // image/png + assert.strictEqual(validateInput('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIu', 'IMAGE'), true) // image/jpeg + assert.strictEqual(validateInput('data:image/gif;base64,R0lGODlhEAAQAMIAAAAAAP///wAAACH5BAEAAAAALAAAAAAQABAAAAIhIhIhIhIhIhIhIhIhIh', 'IMAGE'), true) // image/gif + assert.strictEqual(validateInput('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci', 'IMAGE'), true) // image/svg + assert.strictEqual(validateInput('data:image/bmp;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci', 'IMAGE'), true) // image/bmp + assert.strictEqual(validateInput('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'IMAGE'), true) // allow all types of format + + assert.strictEqual(validateInput('image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'IMAGE'), false) // Missing data:image/ + assert.strictEqual(validateInput('data:image/png,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'IMAGE'), false) // ;base64, + assert.strictEqual(validateInput('data:image/png;base64,iVBORw0KGgo@@@AAAAUA', 'IMAGE'), false) // incorrect base64 + assert.strictEqual(validateInput('data:image/png;base64,', 'IMAGE'), false) // incorrect string, no base64 + assert.strictEqual(validateInput('data:image/png;base64,abcde==!#%&', 'IMAGE'), false) // Missing base64 + }) + + it('FILE, validate data uri', () => { + assert.strictEqual(validateInput('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'FILE'), true) // image/png + assert.strictEqual(validateInput('data:application/pdf;base64,JVBERi0xLjQKJcfs', 'FILE'), true) // image/pdf + assert.strictEqual(validateInput('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU', 'FILE'), true) // video/mp4 + assert.strictEqual(validateInput('data:video/webm;base64,GkXFo3zqEKZxKbOQGh', 'FILE'), true) // video/webm + assert.strictEqual(validateInput('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci', 'FILE'), true) // image/svg + assert.strictEqual(validateInput('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkL', 'FILE'), true) // image/jpeg + assert.strictEqual(validateInput('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'FILE'), true) // allow all types of format + + assert.strictEqual(validateInput('data:image;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'FILE'), false) // Missing media type + assert.strictEqual(validateInput('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==', 'FILE'), false) // Invalid media type + assert.strictEqual(validateInput('data:application/json;base64,{"key":"value"}', 'FILE'), false) // Invalid media type + assert.strictEqual(validateInput('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU...', 'FILE'), false) // Correct type but incomplete format + assert.strictEqual(validateInput('data:image/png;base64 iVBORw0KGgoAAAANSUhEUgAAAAUA', 'FILE'), false) // Missing the comma + assert.strictEqual(validateInput('data://image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'FILE'), false) // Incorrect syntax + assert.strictEqual(validateInput('data:image/png;base64,', 'FILE'), false) // No Base64 data + assert.strictEqual(validateInput('data:image/png;base64,abcde==!', 'FILE'), false) // Invalid characters + assert.strictEqual(validateInput('data:image/jpeg;base64,abc@123', 'FILE'), false) // Invalid characters in Base64 + }) + + it('FILENAME, validate file name', () => { + assert.strictEqual(validateInput('valid-file.txt', 'FILENAME'), true) + assert.strictEqual(validateInput('another_valid_file123', 'FILENAME'), true) + assert.strictEqual(validateInput('file_with_spaces (and more).jpg', 'FILENAME'), true) + assert.strictEqual(validateInput('file-name_1234.pdf', 'FILENAME'), true) + assert.strictEqual(validateInput(' ', 'FILENAME'), true) // Space should be valid + assert.strictEqual(validateInput('...', 'FILENAME'), true) // Dots should be valid + assert.strictEqual(validateInput('-_', 'FILENAME'), true) // Dash and underscore should be valid + + // Special chars + assert.strictEqual(validateInput('file@name_with_special&chars.txt', 'FILENAME'), false) + assert.strictEqual(validateInput('file.name;with,commas:andthan>symbols', 'FILENAME'), false) + assert.strictEqual(validateInput('file_with_special_chars&%', 'FILENAME'), false) + + assert.strictEqual(validateInput('invalid/file.txt', 'FILENAME'), false) // Contains / + assert.strictEqual(validateInput('invalid\\file.txt', 'FILENAME'), false) // Contains \ + assert.strictEqual(validateInput('invalid:file.txt', 'FILENAME'), false) // Contains : + assert.strictEqual(validateInput('invalid*file.txt', 'FILENAME'), false) // Contains * + assert.strictEqual(validateInput('invalid|file.txt', 'FILENAME'), false) // Contains | + assert.strictEqual(validateInput('invalidfile.txt', 'FILENAME'), false) // Contains > + assert.strictEqual(validateInput('', 'FILENAME'), false) // Empty string + assert.strictEqual(validateInput('invalid?file.txt', 'FILENAME'), false) // Contains ? + }) + + it('ADMIN_PASSWORD, validate password', () => { + // valid admin password + assert.strictEqual(validateInput('A1!strongpass', 'PASSWORD'), true) + assert.strictEqual(validateInput('Secure#Password1', 'PASSWORD'), true) + assert.strictEqual(validateInput('Another@Valid3Password', 'PASSWORD'), true) + assert.strictEqual(validateInput('Passw0rd!', 'PASSWORD'), true) + assert.strictEqual(validateInput('1234Abc!@#$', 'PASSWORD'), true) + assert.strictEqual(validateInput('Valid1Password&', 'PASSWORD'), true) + assert.strictEqual(validateInput('MyP@ssw0rd!', 'PASSWORD'), true) + assert.strictEqual(validateInput('Uppercase1!', 'PASSWORD'), true) // No lowercase letters + + assert.strictEqual(validateInput('short1!', 'PASSWORD'), false) // Too short + assert.strictEqual(validateInput('nospecialchars', 'PASSWORD'), false) // No special characters + assert.strictEqual(validateInput('NOLOWERCASE1!', 'PASSWORD'), false) // No lowercase letters + assert.strictEqual(validateInput('nouppercase1!', 'PASSWORD'), false) // No uppercase letters + assert.strictEqual(validateInput('NoDigits!', 'PASSWORD'), false) // No digits + assert.strictEqual(validateInput('NoSpecialChars1', 'PASSWORD'), false) // No special characters + assert.strictEqual(validateInput('lowercase1!', 'PASSWORD'), false) // No uppercase letters + assert.strictEqual(validateInput('12345678!', 'PASSWORD'), false) // No letters + assert.strictEqual(validateInput('Password!', 'PASSWORD'), false) // No digits + }) +}) diff --git a/test/validations.js b/test/validations.js deleted file mode 100644 index 1cdc61c..0000000 --- a/test/validations.js +++ /dev/null @@ -1,266 +0,0 @@ -'use strict' - -/* eslint-env mocha */ - -const assert = require('assert') -const { validations } = require('../index') - -const { - NUMBER, - EMAIL, - PATH, - NAME, - NAME_WITH_DIGITS, - INPUT, - ADDRESS, - PHONE_CODE, - IMAGE, - FILE, - FILENAME, - ADMIN_PASSWORD -} = validations - -describe('validations', () => { - it('NUMBER, validate numbers', () => { - assert.strictEqual(NUMBER.test('100'), true) - - assert.strictEqual(NUMBER.test('-100'), false) - assert.strictEqual(NUMBER.test('1.2'), false) - assert.strictEqual(NUMBER.test('.2'), false) - assert.strictEqual(NUMBER.test('-0'), false) - assert.strictEqual(NUMBER.test('string'), false) - }) - - it('EMAIL, validate email addresses', () => { - assert.strictEqual(EMAIL.test('test5678@email.com'), true) - assert.strictEqual(EMAIL.test('test5678test@email.com'), true) - assert.strictEqual(EMAIL.test('test5678@email123.com'), true) - assert.strictEqual(EMAIL.test('test5678@email123.io'), true) - assert.strictEqual(EMAIL.test('test5678@email123.io123'), true) - - assert.strictEqual(NUMBER.test('abc'), false) - assert.strictEqual(NUMBER.test('5678test@email.com'), false) - assert.strictEqual(NUMBER.test('test@123email.com'), false) - assert.strictEqual(NUMBER.test('test@123email.324'), false) - }) - - it('PATH, validate path, url', () => { - assert.strictEqual(PATH.test('/'), true) - assert.strictEqual(PATH.test('/path'), true) - assert.strictEqual(PATH.test('path/'), true) - assert.strictEqual(PATH.test('/path/test'), true) - assert.strictEqual(PATH.test('//'), true) - assert.strictEqual(PATH.test('/'), true) - assert.strictEqual(PATH.test('a/b/c/d/e/f/g/c/d/f/k/d/'), true) - assert.strictEqual(PATH.test('#'), true) - assert.strictEqual(PATH.test('/3'), true) - - assert.strictEqual(PATH.test(''), true) - assert.strictEqual(PATH.test('//abc'), true) - }) - - it('NAME, validate NAME', () => { - assert.strictEqual(NAME.test('こんにちは'), true) // Japanese - assert.strictEqual(NAME.test('你好'), true) // Chinese - assert.strictEqual(NAME.test('Здравствуйте'), true) // Russian - - // Name with digits - assert.strictEqual(NAME.test('こんにちは3'), false) // Japanese - assert.strictEqual(NAME.test('你好4'), false) // Chinese - assert.strictEqual(NAME.test('Здравствуйте4'), false) // Russian - - assert.strictEqual(NAME.test('*'), false) // special characters - assert.strictEqual(NAME.test('😊'), false) // emoji - }) - - it('INPUT, validate input text', () => { - // input with/without digits - assert.strictEqual(INPUT.test('こんにちは'), true) // Japanese - assert.strictEqual(INPUT.test('你好'), true) // Chinese - assert.strictEqual(INPUT.test('Здравствуйте'), true) // Russian - - assert.strictEqual(INPUT.test('こんにちは3'), true) // Japanese - assert.strictEqual(INPUT.test('你好4'), true) // Chinese - assert.strictEqual(INPUT.test('Здравствуйте4'), true) // Russian - - assert.strictEqual(INPUT.test('*'), false) // special characters - assert.strictEqual(INPUT.test('😊'), false) // emoji - }) - - it('NAME_WITH_DIGITS, validate name with digits', () => { - assert.strictEqual(NAME_WITH_DIGITS.test('こんにちは3'), true) // Japanese - assert.strictEqual(NAME_WITH_DIGITS.test('你好4'), true) // Chinese - assert.strictEqual(NAME_WITH_DIGITS.test('Здравствуйте4'), true) // Russian - - // without digits - assert.strictEqual(NAME_WITH_DIGITS.test('こんにちは'), true) // Japanese - assert.strictEqual(NAME_WITH_DIGITS.test('你好'), true) // Chinese - assert.strictEqual(NAME_WITH_DIGITS.test('Здравствуйте'), true) // Russian - - assert.strictEqual(NAME_WITH_DIGITS.test('*'), false) // special characters - assert.strictEqual(NAME_WITH_DIGITS.test('😊'), false) // emoji - }) - - it('ADDRESS, validate address text', () => { - assert.strictEqual(ADDRESS.test('123 Main Street, Springfield, IL 62701, USA'), true) // english - assert.strictEqual(ADDRESS.test('東京都新宿区西新宿2丁目8-1'), true) // Japanese - assert.strictEqual(ADDRESS.test('北京市朝阳区建国门外大街甲12'), true) // Chinese - assert.strictEqual(ADDRESS.test('г. Москва, ул. Тверская, д. 15'), true) // Russian - assert.strictEqual(ADDRESS.test('м. Київ, вул. Хрещатик, 22'), true) // Ukrainian - - // Simple addresses - assert.strictEqual(ADDRESS.test('123 Main St.'), true) - assert.strictEqual(ADDRESS.test('456 Elm Street; Apt #2'), true) - assert.strictEqual(ADDRESS.test('789 Maple Ave, Unit 5'), true) - assert.strictEqual(ADDRESS.test('321 Broadway St: Suite 100'), true) - - // punctuation in address - assert.strictEqual(ADDRESS.test('15-20 First Ave.'), true) - assert.strictEqual(ADDRESS.test('30/40 Oak Drive'), true) - assert.strictEqual(ADDRESS.test('25:40 Second St.'), true) - assert.strictEqual(ADDRESS.test('47:50 3rd Blvd; Apt 4B'), true) - - // Including special character - assert.strictEqual(ADDRESS.test('Street Name & Co.'), true) - assert.strictEqual(ADDRESS.test('First St. + Second St.'), true) - assert.strictEqual(ADDRESS.test('Apt #123 & 456'), true) - assert.strictEqual(ADDRESS.test('Cross St @ 78'), true) - - // Address with new line - assert.strictEqual(ADDRESS.test('123 Main St\nApt 4'), true) - assert.strictEqual(ADDRESS.test('456 Elm St\nSuite B'), true) - - // Complex address - assert.strictEqual(ADDRESS.test('221B Baker Street, London'), true) - assert.strictEqual(ADDRESS.test('1234 Example Rd, City, State 56789'), true) - assert.strictEqual(ADDRESS.test('PO Box 1234'), true) - assert.strictEqual(ADDRESS.test('456 High St. #10, New York, NY 10001'), true) - assert.strictEqual(ADDRESS.test('456 Elm St@'), true) // special character - - // Leading/trailing spaces - assert.strictEqual(ADDRESS.test(' 123 Main St '), true) // leading/trailing space - assert.strictEqual(ADDRESS.test('#456 Elm St'), true) // leading character - assert.strictEqual(ADDRESS.test('Unit 5; Apt #'), true) // leading character - assert.strictEqual(ADDRESS.test('22 Main St? Apt'), true) // question mark - assert.strictEqual(ADDRESS.test('\n'), true) // newline - - // Invalid characters - assert.strictEqual(ADDRESS.test('123 Main St!'), false) // Exclamation mark - assert.strictEqual(ADDRESS.test('789 Maple Ave#1*'), false) // asterisk - - assert.strictEqual(ADDRESS.test(''), false) // (empty) - - // // invalid characters - assert.strictEqual(ADDRESS.test('!!!'), false) - // assert.strictEqual(ADDRESS.test('@@@'), false) - assert.strictEqual(ADDRESS.test('123*456'), false) // contains asterisk - - // // improper address format - assert.strictEqual(ADDRESS.test('First St $ Apt 4'), false) // dollar sign - - assert.strictEqual(ADDRESS.test('*'), false) // special characters - assert.strictEqual(ADDRESS.test('😊'), false) // emoji - }) - - it('PHONE_CODE, validate phone code', () => { - assert.strictEqual(PHONE_CODE.test('+123'), true) // single plus sign - assert.strictEqual(PHONE_CODE.test('++456'), true) // multiple plus sign - assert.strictEqual(PHONE_CODE.test('789'), true) // no plus sign - assert.strictEqual(PHONE_CODE.test('+1'), true) // plus with digit - assert.strictEqual(PHONE_CODE.test('+123456'), true) // plus with multiple digit - assert.strictEqual(PHONE_CODE.test('0'), true) // only digit - assert.strictEqual(PHONE_CODE.test('+0'), true) // leading plus sign with zero - - assert.strictEqual(PHONE_CODE.test('+12a34'), false) // plus sign followed by non-digts - assert.strictEqual(PHONE_CODE.test('a123'), false) // leading character - assert.strictEqual(PHONE_CODE.test('++'), false) // double plus sign - assert.strictEqual(PHONE_CODE.test(''), false) // (empty) - assert.strictEqual(PHONE_CODE.test('+ 123'), false) // spaces around number - assert.strictEqual(PHONE_CODE.test('+123abc'), false) // invalid characters mixed - assert.strictEqual(PHONE_CODE.test('-123'), false) // negative sign - assert.strictEqual(PHONE_CODE.test('*'), false) // special characters - assert.strictEqual(PHONE_CODE.test('😊'), false) // emoji - }) - - it('IMAGE, validate image uri', () => { - assert.strictEqual(IMAGE.test('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // image/png - assert.strictEqual(IMAGE.test('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIu'), true) // image/jpeg - assert.strictEqual(IMAGE.test('data:image/gif;base64,R0lGODlhEAAQAMIAAAAAAP///wAAACH5BAEAAAAALAAAAAAQABAAAAIhIhIhIhIhIhIhIhIhIh'), true) // image/gif - assert.strictEqual(IMAGE.test('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/svg - assert.strictEqual(IMAGE.test('data:image/bmp;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/bmp - assert.strictEqual(IMAGE.test('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // allow all types of format - - assert.strictEqual(IMAGE.test('image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing data:image/ - assert.strictEqual(IMAGE.test('data:image/png,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // ;base64, - assert.strictEqual(IMAGE.test('data:image/png;base64,iVBORw0KGgo@@@AAAAUA'), false) // incorrect base64 - assert.strictEqual(IMAGE.test('data:image/png;base64,'), false) // incorrect string, no base64 - assert.strictEqual(IMAGE.test('data:image/png;base64,abcde==!#%&'), false) // Missing base64 - }) - - it('FILE, validate data uri', () => { - assert.strictEqual(FILE.test('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // image/png - assert.strictEqual(FILE.test('data:application/pdf;base64,JVBERi0xLjQKJcfs'), true) // image/pdf - assert.strictEqual(FILE.test('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU'), true) // video/mp4 - assert.strictEqual(FILE.test('data:video/webm;base64,GkXFo3zqEKZxKbOQGh'), true) // video/webm - assert.strictEqual(FILE.test('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmci'), true) // image/svg - assert.strictEqual(FILE.test('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkL'), true) // image/jpeg - assert.strictEqual(FILE.test('data:image/xyz;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), true) // allow all types of format - - assert.strictEqual(FILE.test('data:image;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing media type - assert.strictEqual(FILE.test('data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='), false) // Invalid media type - assert.strictEqual(FILE.test('data:application/json;base64,{"key":"value"}'), false) // Invalid media type - assert.strictEqual(FILE.test('data:video/mp4;base64,AAAAFGZ0eXBNUDANCk1vZGU...'), false) // Correct type but incomplete format - assert.strictEqual(FILE.test('data:image/png;base64 iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Missing the comma - assert.strictEqual(FILE.test('data://image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA'), false) // Incorrect syntax - assert.strictEqual(FILE.test('data:image/png;base64,'), false) // No Base64 data - assert.strictEqual(FILE.test('data:image/png;base64,abcde==!'), false) // Invalid characters - assert.strictEqual(FILE.test('data:image/jpeg;base64,abc@123'), false) // Invalid characters in Base64 - }) - - it('FILENAME, validate file name', () => { - assert.strictEqual(FILENAME.test('valid-file.txt'), true) - assert.strictEqual(FILENAME.test('another_valid_file123'), true) - assert.strictEqual(FILENAME.test('file_with_spaces (and more).jpg'), true) - assert.strictEqual(FILENAME.test('file-name_1234.pdf'), true) - assert.strictEqual(FILENAME.test(' '), true) // Space should be valid - assert.strictEqual(FILENAME.test('...'), true) // Dots should be valid - assert.strictEqual(FILENAME.test('-_'), true) // Dash and underscore should be valid - - // Special chars - assert.strictEqual(FILENAME.test('file@name_with_special&chars.txt'), false) - assert.strictEqual(FILENAME.test('file.name;with,commas:andthan>symbols'), false) - assert.strictEqual(FILENAME.test('file_with_special_chars&%'), false) - - assert.strictEqual(FILENAME.test('invalid/file.txt'), false) // Contains / - assert.strictEqual(FILENAME.test('invalid\\file.txt'), false) // Contains \ - assert.strictEqual(FILENAME.test('invalid:file.txt'), false) // Contains : - assert.strictEqual(FILENAME.test('invalid*file.txt'), false) // Contains * - assert.strictEqual(FILENAME.test('invalid|file.txt'), false) // Contains | - assert.strictEqual(FILENAME.test('invalidfile.txt'), false) // Contains > - assert.strictEqual(FILENAME.test(''), false) // Empty string - assert.strictEqual(FILENAME.test('invalid?file.txt'), false) // Contains ? - }) - - it('ADMIN_PASSWORD, validate password', () => { - // valid admin password - assert.strictEqual(ADMIN_PASSWORD.test('A1!strongpass'), true) - assert.strictEqual(ADMIN_PASSWORD.test('Secure#Password1'), true) - assert.strictEqual(ADMIN_PASSWORD.test('Another@Valid3Password'), true) - assert.strictEqual(ADMIN_PASSWORD.test('Passw0rd!'), true) - assert.strictEqual(ADMIN_PASSWORD.test('1234Abc!@#$'), true) - assert.strictEqual(ADMIN_PASSWORD.test('Valid1Password&'), true) - assert.strictEqual(ADMIN_PASSWORD.test('MyP@ssw0rd!'), true) - assert.strictEqual(ADMIN_PASSWORD.test('Uppercase1!'), true) // No lowercase letters - - assert.strictEqual(ADMIN_PASSWORD.test('short1!'), false) // Too short - assert.strictEqual(ADMIN_PASSWORD.test('nospecialchars'), false) // No special characters - assert.strictEqual(ADMIN_PASSWORD.test('NOLOWERCASE1!'), false) // No lowercase letters - assert.strictEqual(ADMIN_PASSWORD.test('nouppercase1!'), false) // No uppercase letters - assert.strictEqual(ADMIN_PASSWORD.test('NoDigits!'), false) // No digits - assert.strictEqual(ADMIN_PASSWORD.test('NoSpecialChars1'), false) // No special characters - assert.strictEqual(ADMIN_PASSWORD.test('lowercase1!'), false) // No uppercase letters - assert.strictEqual(ADMIN_PASSWORD.test('12345678!'), false) // No letters - assert.strictEqual(ADMIN_PASSWORD.test('Password!'), false) // No digits - }) -}) From e189708397829d661182a6bd67c6945e4ff6e1eb Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Wed, 16 Oct 2024 17:58:38 +1100 Subject: [PATCH 5/6] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff78a23..26d4d6c 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,5 @@ Currently supported utils: - `sum` - calculate sum of array items - `sumBy` - calculate sum of array items using iteratee function or string shortcut - `uniqBy` - get unique values of array by the iteratee function or property path +- `validateInput` - validates the input based on the regex format options: `NUMBER | EMAIL | PATH | NAME | NAME_WITH_DIGITS | INPUT | ADDRESS | PHONE_CODE | IMAGE | FILE | FILENAME | PASSWORD` - `without` - creates an array of values from the first argument excluding all given arguments -- `[ NUMBER, EMAIL, PATH, NAME, NAME_WITH_DIGITS, INPUT, ADDRESS, PHONE_CODE, IMAGE, FILE, FILENAME, ADMIN_PASSWORD ]` - list of regex validations From 9d89e29749a5c9d819604bfc95d6cd99cfc2063a Mon Sep 17 00:00:00 2001 From: muhammadbilal Date: Mon, 21 Oct 2024 23:01:49 +1100 Subject: [PATCH 6/6] Add validations for international phone numbers --- README.md | 2 +- src/validateInput.js | 2 ++ test/validateInput.js | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26d4d6c..ca263e4 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,5 @@ Currently supported utils: - `sum` - calculate sum of array items - `sumBy` - calculate sum of array items using iteratee function or string shortcut - `uniqBy` - get unique values of array by the iteratee function or property path -- `validateInput` - validates the input based on the regex format options: `NUMBER | EMAIL | PATH | NAME | NAME_WITH_DIGITS | INPUT | ADDRESS | PHONE_CODE | IMAGE | FILE | FILENAME | PASSWORD` +- `validateInput` - validates the input based on the regex format options: `NUMBER | EMAIL | PATH | NAME | NAME_WITH_DIGITS | INPUT | ADDRESS | PHONE_CODE | PHONE | IMAGE | FILE | FILENAME | PASSWORD` - `without` - creates an array of values from the first argument excluding all given arguments diff --git a/src/validateInput.js b/src/validateInput.js index 0dbcace..e1e486b 100644 --- a/src/validateInput.js +++ b/src/validateInput.js @@ -16,6 +16,7 @@ const NAME_WITH_DIGITS = new RegExp(`^[-${CHARACTERS}${DIGITS}' &()/]+$`, 'u') const INPUT = new RegExp(`^[${INPUT_BASE_REG_EXP.source}]+$`, 'u') const ADDRESS = new RegExp(`^[${INPUT_BASE_REG_EXP.source}#]+$`, 'u') const PHONE_CODE = /^(\+?\d{1,3}|\d{1,4})$/ +const PHONE = /^\+?(\d{1,3}|\d{1,4})?[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}$/ const IMAGE = /^data:image\/[A-Za-z+]+;base64,[A-Za-z0-9+/=]+$/ const FILE = /^data:(image|application|video)\/[A-Za-z0-9+]+;base64,[A-Za-z0-9+/=]+$/ const FILENAME = new RegExp(`^[-${DIGITS}.${CHARACTERS} ()_]+$`, 'u') @@ -36,6 +37,7 @@ const validateInput = (input, format) => { case 'PATH': return PATH.test(input) case 'INPUT': return INPUT.test(input) case 'ADDRESS': return ADDRESS.test(input) + case 'PHONE': return PHONE.test(input) case 'PHONE_CODE': return PHONE_CODE.test(input) case 'IMAGE': return IMAGE.test(input) case 'FILE': return FILE.test(input) diff --git a/test/validateInput.js b/test/validateInput.js index 3de3314..cb4bddf 100644 --- a/test/validateInput.js +++ b/test/validateInput.js @@ -170,6 +170,29 @@ describe('validations', () => { assert.strictEqual(validateInput('😊', 'PHONE_CODE'), false) // emoji }) + it('PHONE, validate phone code', () => { + assert.strictEqual(validateInput('+1 234 567 8900', 'PHONE'), true) + assert.strictEqual(validateInput('123 45 678 9334', 'PHONE'), true) + assert.strictEqual(validateInput('+12 3456', 'PHONE'), true) + assert.strictEqual(validateInput('+44 20 1234 5678', 'PHONE'), true) + assert.strictEqual(validateInput('123-456-7890', 'PHONE'), true) + assert.strictEqual(validateInput('(123) 456-7890', 'PHONE'), true) + assert.strictEqual(validateInput('+91 (22) 1234 5678', 'PHONE'), true) + assert.strictEqual(validateInput('+49 30 123456', 'PHONE'), true) + assert.strictEqual(validateInput('001 234 567 8900', 'PHONE'), true) + assert.strictEqual(validateInput('+81 3-1234-5678', 'PHONE'), true) + assert.strictEqual(validateInput('0044 20 1234 5678', 'PHONE'), true) + assert.strictEqual(validateInput('12-34-56-78', 'PHONE'), true) + assert.strictEqual(validateInput('123-456-78 90', 'PHONE'), true) + + assert.strictEqual(validateInput('abc-def-ghij', 'PHONE'), false) // Non-numeric + assert.strictEqual(validateInput('+44 1234 56789 0123', 'PHONE'), false) // Too long + assert.strictEqual(validateInput('+12 34-56-78-90-12', 'PHONE'), false) // Too many parts + assert.strictEqual(validateInput('+33 1 23 45 67 89', 'PHONE'), false) // Too many parts + assert.strictEqual(validateInput('++123 456', 'PHONE'), false) // Extra plus sign + assert.strictEqual(validateInput('123 456 7890a', 'PHONE'), false) // Extra character + }) + it('IMAGE, validate image uri', () => { assert.strictEqual(validateInput('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', 'IMAGE'), true) // image/png assert.strictEqual(validateInput('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIu', 'IMAGE'), true) // image/jpeg