/* This module have to be independant of any external package it is shared between back and front and is usefull to apply common check in front before sending it in back can be include in project with - into a browser : - into a node.js : const check = require( `../nationchains/socialworld/contracts/check.js`); */ // --## const check = {}; check.schema = {}; check.schema.properties = {}; check.schema.properties.type = {}; check.schema.properties.type.string = (str) => typeof str === "string"; check.schema.properties.type.number = (n) => typeof n === "number"; check.schema.properties.type.integer = (n) => n != "" && !isNaN(n) && Math.round(n) == n; check.schema.properties.type.float = (n) => n != "" && !isNaN(n) && Math.round(n) != n; //not yet in json schema check.schema.properties.minLength = (str, min) => typeof str === "string" && str.length > parseInt(min); check.schema.properties.maxLength = (str, max) => typeof str === "string" && str.length < parseInt(max); check.schema.properties.multipleOf = (n, val) => typeof n === "number" && typeof val === "number" && parseFloat(n) / parseFloat(val) - Math.round(parseFloat(n) / parseFloat(val)) < 0.0000001; check.schema.properties.range = ( n, minimum, exclusiveMinimum, maximum, exclusiveMaximum ) => { //console.log(minimum,exclusiveMinimum,maximum, exclusiveMaximum,n) if (typeof n !== "number") return false; if (minimum && parseFloat(n) < parseFloat(minimum)) return false; if (exclusiveMinimum && parseFloat(n) <= parseFloat(exclusiveMinimum)) return false; if (maximum && parseFloat(n) > parseFloat(maximum)) return false; if (exclusiveMaximum && parseFloat(n) >= parseFloat(exclusiveMaximum)) return false; return true; }; check.schema.properties.pattern = (str, pattern) => { try { new RegExp(pattern); } catch (e) { return false; } return pattern.test(str); }; check.schema.properties.enum = (str, enumvalues) => typeof str === "string" && enumvalues.includes(str); // see format https://json-schema.org/understanding-json-schema/reference/string.html#format check.schema.properties.format = { "date-time": / /, time: / /, date: / /, duration: / /, email: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, "idn-email": / /, uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, uri: / /, "uri-reference": / /, iri: / /, hostname: / /, "idn-hostname": / /, ipv4: /^([0–9]{1,3}.){3}.([0–9]{1,3})$/, ipv6: /^((([0–9A-Fa-f]{1,4}:){7}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}:[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){5}:([0–9A-Fa-f]{1,4}:)?[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){4}:([0–9A-Fa-f]{1,4}:){0,2}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){3}:([0–9A-Fa-f]{1,4}:){0,3}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){2}:([0–9A-Fa-f]{1,4}:){0,4}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(([0–9A-Fa-f]{1,4}:){0,5}:((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(::([0–9A-Fa-f]{1,4}:){0,5}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|([0–9A-Fa-f]{1,4}::([0–9A-Fa-f]{1,4}:){0,5}[0–9A-Fa-f]{1,4})|(::([0–9A-Fa-f]{1,4}:){0,6}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){1,7}:))$/, telephonefr: /^0[1-9][0-9]{9}$/, telephoneinter: /^\+*(\d{3})*[0-9,\-]{8,}/, password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/, postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/, }; check.schema.validation = (schema) => { /*validate a schema structure*/ const res = { status: 200, err: [] }; if (schema.properties) { Object.keys(schema.properties).forEach((p) => { const properties = schema.properties; if ( properties[p].type && typeof properties[p].type === "string" && !check.schema.properties.type[properties[p].type] ) { res.err.push({ info: "|checkdata|typedoesnotexistinschema", moreinfo: ` ${properties[p].type}`, }); } if ( properties[p].type && typeof properties[p].type === "object" && Array.isArray(properties[p].type) ) { properties[p].type.forEach((tp) => { if (!check.schema.properties.type[tp]) res.err.push({ info: "|checkdata|typedoesnotexistinschema", moreinfo: `${tp} of ${properties[p].type}`, }); }); } if ( properties[p].format && !check.schema.properties.format[properties[p].format] ) { res.err.push({ info: "|checkdata|formatdoesnotexistinschema", moreinfo: ` ${properties[p].format}`, }); } if (properties[p].enum && !Array.isArray(properties[p].enum)) { res.err.push({ info: "|checkdata|enumisnotarrayinschema", moreinfo: ` ${properties[p].enum}`, }); } }); } // 406 means not acceptable if (res.err.length > 0) res.status = 406; return res; }; check.schema.data = (schema, ctx, data) => { /* validate a data set with a schema in a context ctx */ /* console.log('#################') console.log(schema); console.log('---------') console.log(data) */ const validschema = check.schema.validation(schema); if (validschema.status != 200) return validschema; const res = { status: 200, err: [] }; if (schema.properties) { const properties = schema.properties; Object.keys(properties).forEach((p) => { //type is mandatory in a propertie if (data[p]) { const typlist = properties[p].type && typeof properties[p].type === "string" ? [properties[p].type] : properties[p].type; let valid = false; typlist.forEach((typ) => { // at least one test have to be ok if (check.schema.properties.type[typ](data[p])) valid = true; }); if (!valid) res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]}`, }); if ( properties[p].minLength && !check.schema.properties.minLength(data[p], properties[p].minLength) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} minLength:${properties[p].minLength}`, }); } if ( properties[p].maxLength && !check.schema.properties.maxLength(data[p], properties[p].maxLength) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} maxLength:${properties[p].maxLength}`, }); } if ( properties[p].multipleOf && !check.schema.properties.multipleOf(data[p], properties[p].multipleOf) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} not a multipleOf:${properties[p].multipleOf}`, }); } if ( properties[p].minimum || properties[p].maximum || properties[p].exclusiveMinimum || properties[p].exclusiveMaximum ) { // test range if ( !check.schema.properties.range( data[p], properties[p].minimum, properties[p].exclusiveMinimum, properties[p].maximum, properties[p].exclusiveMaximum ) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} not in range ${properties[p].minimum} exclu: ${properties[p].exclusiveMinimum} and ${properties[p].maximum} exclu: ${properties[p].exclusiveMaximum}`, }); } } if ( properties[p].enum && !check.schema.properties.enum(data[p], properties[p].enum) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} not in enum list :${properties[p].enum}`, }); } if (properties[p].format) { properties[p].pattern = check.schema.properties.format[properties[p].format]; } if ( properties[p].pattern && !check.schema.properties.pattern(data[p], properties[p].pattern) ) { res.err.push({ info: "|checkdata|dataerrpropertie", moreinfo: `${p} : ${data[p]} problem pattern or format ${properties[p].pattern}`, }); } } else if (schema.required.includes(p)) { res.err.push({ info: "|checkdata|dataerrpropertiesrequired", moreinfo: `${p}`, }); } }); } if (res.err.length > 0) res.status = 417; return res; }; /* Normalize data link to check.schema.properties.format or any normalization to get consistent data */ const normalize={}; normalize.telephonefr =(phone)=>{ phone = phone.trim().replace(/[- .]/g, ""); if ( check.schema.properties.format.telephoenfr(phone) && phone.length == 10 && phone[0] == "0" ) { phone = "+33 " + phone.substring(1); } return phone; }; normalize.zfill10 = (num) => { let s = num + ""; while (s.length < 10) s = "0" + s; return s; }; check.test.unique = (ctx, val) => { if (ctx.list[ctx.currentfield]) { return !ctx.list[ctx.currentfield].includes(val); } else { console.log("ERR no list for field:" + ctx.currentfield); return false; } }; // check.normalize take a correct data then reformat it to harmonise it check.normalize = {}; check.normalize.phoneNumber = (ctx, phone) => { phone = phone.trim().replace(/[- .]/g, ""); if ( check.test.phoneNumber("", phone) && phone.length == 10 && phone[0] == "0" ) { phone = "+33 " + phone.substring(1); } return phone; }; check.normalize.upperCase = (ctx, txt) => txt.toUpperCase(); check.normalize.lowerCase = (ctx, txt) => txt.toLowerCase(); // fixe 10 position et complete par des 0 devant check.normalize.zfill10 = (ctx, num) => { let s = num + ""; while (s.length < 10) s = "0" + s; return s; }; if (typeof module !== "undefined") module.exports = check;