| 1 | try { var _ = require("underscore"); } catch (err) {} //for server |
| 2 | |
| 3 | let Validator = { }; |
| 4 | |
| 5 | // Cell in evaluation.questions array |
| 6 | Validator.Question = { |
| 7 | "index": "section", //"2.2.1", "3.2", "1" ...etc |
| 8 | "wording": "string", |
| 9 | "options": "stringArray", //only for quiz |
| 10 | "fixed": "boolean", |
| 11 | "points": "number", |
| 12 | }; |
| 13 | |
| 14 | Validator.Answer = { |
| 15 | "index": "section", |
| 16 | "value": "stringOrIntegerArray", |
| 17 | }; |
| 18 | |
| 19 | Validator.Input = { |
| 20 | "index": "section", |
| 21 | "input": "stringOrIntegerArray", |
| 22 | }; |
| 23 | |
| 24 | // One student response to an exam |
| 25 | Validator.Paper = { |
| 26 | "number": "code", |
| 27 | "inputs": Validator.Input, |
| 28 | "startTime": "positiveInteger", |
| 29 | "current": "positiveInteger", |
| 30 | "discoTime": "positiveInteger", |
| 31 | "discoCount": "positiveInteger", |
| 32 | "totalDisco": "positiveInteger", |
| 33 | "password": "password", |
| 34 | }; |
| 35 | |
| 36 | Validator.Evaluation = { |
| 37 | "_id": "bson", |
| 38 | "cid": "bson", |
| 39 | "name": "code", |
| 40 | "mode": "alphanumeric", //"open" or "exam", but alphanumeric is good enough |
| 41 | "active": "boolean", |
| 42 | "fixed": "boolean", |
| 43 | "display": "alphanumeric", //"one" or "all" |
| 44 | "time": "integer", |
| 45 | "introduction": "string", |
| 46 | "coefficient": "number", |
| 47 | "questions": Validator.Question, |
| 48 | "answers": Validator.Answer, |
| 49 | "papers": Validator.Paper, |
| 50 | }; |
| 51 | |
| 52 | Validator.User = { |
| 53 | "_id": "bson", |
| 54 | "email": "email", |
| 55 | "name": "name", |
| 56 | "initials": "alphanumeric", |
| 57 | "loginToken": "alphanumeric", |
| 58 | "sessionTokens": "alphanumericArray", |
| 59 | }; |
| 60 | |
| 61 | Validator.Student = { |
| 62 | "number": "code", |
| 63 | "name": "name", |
| 64 | "group": "positiveInteger", |
| 65 | }; |
| 66 | |
| 67 | Validator.Course = { |
| 68 | "_id": "bson", |
| 69 | "uid": "bson", |
| 70 | "code": "code", |
| 71 | "description": "string", |
| 72 | "password": "hash", |
| 73 | "students": Validator.Student, |
| 74 | }; |
| 75 | |
| 76 | Object.assign(Validator, |
| 77 | { |
| 78 | // Recurse into sub-documents |
| 79 | checkObject_aux: function(obj, model) |
| 80 | { |
| 81 | for (let key of Object.keys(obj)) |
| 82 | { |
| 83 | if (!model[key]) |
| 84 | return "Unknown field"; |
| 85 | if (model[key] == "unchecked") //not a user input (ignored) |
| 86 | continue; |
| 87 | if (_.isObject(model[key])) |
| 88 | { |
| 89 | // TODO: next loop seems too heavy... (only a concern if big class import?) |
| 90 | for (let item of obj[key]) |
| 91 | { |
| 92 | let error = Validator.checkObject_aux(item, model[key]); |
| 93 | if (error.length > 0) |
| 94 | return error; |
| 95 | } |
| 96 | } |
| 97 | else |
| 98 | { |
| 99 | let error = Validator[ "check_" + model[key] ](obj[key]); |
| 100 | if (error.length > 0) |
| 101 | return key + ": " + error; |
| 102 | } |
| 103 | } |
| 104 | return ""; |
| 105 | }, |
| 106 | |
| 107 | // Always check top-level object |
| 108 | checkObject: function(obj, name) |
| 109 | { |
| 110 | return Validator.checkObject_aux(obj, Validator[name]); |
| 111 | }, |
| 112 | |
| 113 | "check_string": function(arg) |
| 114 | { |
| 115 | return ""; //strings are unchecked, but sanitized |
| 116 | }, |
| 117 | |
| 118 | "check_section": function(arg) |
| 119 | { |
| 120 | if (!_.isString(arg)) |
| 121 | return "not a string"; |
| 122 | if (!/^[0-9.]+$/.test(arg)) |
| 123 | return "digits and dot only"; |
| 124 | return ""; |
| 125 | }, |
| 126 | |
| 127 | "check_alphanumeric": function(arg) |
| 128 | { |
| 129 | return arg.match(/^[\w]{1,32}$/) === null ? "[1,32] alphanumerics" : ""; |
| 130 | }, |
| 131 | |
| 132 | "check_bson": function(arg) |
| 133 | { |
| 134 | return arg.match(/^[a-z0-9]{24}$/) === null ? "not a BSON id" : ""; |
| 135 | }, |
| 136 | |
| 137 | "check_name": function(arg) |
| 138 | { |
| 139 | if (!_.isString(arg)) |
| 140 | return "not a string"; |
| 141 | if (!/^[a-zA-Z\u00C0-\u024F -]{1,32}$/.test(arg)) |
| 142 | return "[1,32] letters + hyphen/space"; |
| 143 | return ""; |
| 144 | }, |
| 145 | |
| 146 | "check_email": function(arg) |
| 147 | { |
| 148 | if (!_.isString(arg)) |
| 149 | return "not a string"; |
| 150 | if (arg.length > 64) |
| 151 | return "string too long: max. 64 characters"; |
| 152 | // Regexp used in "type='email'" inputs ( http://emailregex.com/ ) |
| 153 | if (!/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(arg)) |
| 154 | return "X@Y, alphanumerics and . _ - +(X)"; |
| 155 | return ""; |
| 156 | }, |
| 157 | |
| 158 | "check_code": function(arg) |
| 159 | { |
| 160 | if (!_.isString(arg)) |
| 161 | return "not a string"; |
| 162 | if (!/^[\w.-]{1,16}$/.test(arg)) |
| 163 | return "[1,16] alphanumerics and . _ -"; |
| 164 | return ""; |
| 165 | }, |
| 166 | |
| 167 | "check_number": function(arg) |
| 168 | { |
| 169 | if (!_.isNumber(arg)) |
| 170 | arg = parseFloat(arg); |
| 171 | if (isNaN(arg)) |
| 172 | return "not a number"; |
| 173 | return ""; |
| 174 | }, |
| 175 | |
| 176 | "check_integer": function(arg) |
| 177 | { |
| 178 | if (!_.isNumber(arg)) |
| 179 | arg = parseInt(arg); |
| 180 | if (isNaN(arg) || arg % 1 != 0) |
| 181 | return "not an integer"; |
| 182 | return ""; |
| 183 | }, |
| 184 | |
| 185 | "check_positiveInteger": function(arg) |
| 186 | { |
| 187 | return Validator["check_integer"](arg) || (arg<0 ? "not positive" : ""); |
| 188 | }, |
| 189 | |
| 190 | "check_boolean": function(arg) |
| 191 | { |
| 192 | if (!_.isBoolean(arg)) |
| 193 | return "not a boolean"; |
| 194 | return ""; |
| 195 | }, |
| 196 | |
| 197 | "check_password": function(arg) |
| 198 | { |
| 199 | if (!_.isString(arg)) |
| 200 | return "not a string"; |
| 201 | if (!/^[\x21-\x7E]{1,16}$/.test(arg)) |
| 202 | return "{1,16} ASCII characters with code in [33,126]"; |
| 203 | return ""; |
| 204 | }, |
| 205 | |
| 206 | // Sha-1 hash: length 40, hexadecimal |
| 207 | "check_hash": function(arg) |
| 208 | { |
| 209 | if (!_.isString(arg)) |
| 210 | return "not a string"; |
| 211 | if (!/^[a-f0-9]{40}$/.test(arg)) |
| 212 | return "not a sha-1 hash"; |
| 213 | return ""; |
| 214 | }, |
| 215 | |
| 216 | "check_integerArray": function(arg) |
| 217 | { |
| 218 | if (!_.isArray(arg)) |
| 219 | return "not an array"; |
| 220 | for (let i=0; i<arg.length; i++) |
| 221 | { |
| 222 | let error = Validator["check_integer"](arg[i]); |
| 223 | if (error.length > 0) |
| 224 | return error; |
| 225 | } |
| 226 | return ""; |
| 227 | }, |
| 228 | |
| 229 | "check_stringArray": function(arg) |
| 230 | { |
| 231 | if (!_.isArray(arg)) |
| 232 | return "not an array"; |
| 233 | for (let i=0; i<arg.length; i++) |
| 234 | { |
| 235 | if (!_.isString(arg[i])); |
| 236 | return "not a string"; |
| 237 | } |
| 238 | return ""; |
| 239 | }, |
| 240 | |
| 241 | "check_alphanumericArray": function(arg) |
| 242 | { |
| 243 | if (!_.isArray(arg)) |
| 244 | return "not an array"; |
| 245 | for (let i=0; i<arg.length; i++) |
| 246 | { |
| 247 | let error = Validator["check_alphanumeric"](arg[i]); |
| 248 | if (error.length > 0) |
| 249 | return error; |
| 250 | } |
| 251 | return ""; |
| 252 | }, |
| 253 | |
| 254 | "check_stringOrIntegerArray": function(arg) |
| 255 | { |
| 256 | if (!_.isString(arg)) |
| 257 | return Validator["check_integerArray"](arg); |
| 258 | return ""; |
| 259 | }, |
| 260 | }); |
| 261 | |
| 262 | try { module.exports = Validator.checkObject; } catch (err) { } //for server |