Commit | Line | Data |
---|---|---|
e99c53fb BA |
1 | try { var _ = require("underscore"); } catch (err) {} //for server |
2 | ||
3 | let Validator = { }; | |
4 | ||
5 | // Cell in assessment.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 | "answer": "string", //both this and next are mutually exclusive | |
12 | "choice": "integerArray", | |
13 | "active": "boolean", | |
14 | "points": "number", | |
15 | }; | |
16 | ||
17 | Validator.Input = { | |
18 | "index": "section", | |
19 | "input": "stringOrIntegerArray", | |
20 | }; | |
21 | ||
22 | // One student response to an exam | |
23 | Validator.Paper = { | |
24 | "number": "code", | |
25 | // (array of) strings for open questions, arrays of integers for quizzes: | |
26 | "inputs": Validator.Input, | |
27 | "startTime": "positiveInteger", | |
28 | "endTime": "positiveInteger", | |
29c8b391 BA |
29 | "discoTime": "positiveInteger", |
30 | "discoCount": "positiveInteger", | |
31 | "totalDisco": "positiveInteger", | |
e99c53fb BA |
32 | "password": "password", |
33 | }; | |
34 | ||
35 | Validator.Assessment = { | |
36 | "_id": "bson", | |
37 | "cid": "bson", | |
38 | "name": "code", | |
39 | "mode": "alphanumeric", //"open" or "exam", but alphanumeric is good enough | |
40 | "active": "boolean", | |
41 | "fixed": "boolean", | |
42 | "display": "alphanumeric", //"one" or "all" | |
43 | "time": "integer", | |
44 | "introduction": "string", | |
45 | "conclusion": "string", | |
46 | "coefficient": "number", | |
47 | "questions": Validator.Question, | |
48 | "papers": Validator.Paper, | |
49 | }; | |
50 | ||
51 | Validator.User = { | |
52 | "_id": "bson", | |
53 | "email": "email", | |
e99c53fb BA |
54 | "name": "name", |
55 | "initials": "unchecked", //not a user input | |
56 | "loginToken": "unchecked", | |
57 | "sessionTokens": "unchecked", | |
58 | "token": "alphanumeric", //exception: for the purpose of user registration | |
59 | }; | |
60 | ||
61 | Validator.Student = { | |
62 | "number": "code", | |
e99c53fb BA |
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_stringArray": function(arg) | |
128 | { | |
129 | return !_.isArray(arg) ? "not an array" : ""; | |
130 | }, | |
131 | ||
132 | "check_alphanumeric": function(arg) | |
133 | { | |
134 | return arg.match(/^[\w]{1,32}$/) === null ? "[1,32] alphanumerics" : ""; | |
135 | }, | |
136 | ||
137 | "check_bson": function(arg) | |
138 | { | |
139 | return arg.match(/^[a-z0-9]{24}$/) === null ? "not a BSON id" : ""; | |
140 | }, | |
141 | ||
142 | "check_name": function(arg) | |
143 | { | |
144 | if (!_.isString(arg)) | |
145 | return "not a string"; | |
146 | if (!/^[a-zA-Z\u00C0-\u024F -]{1,32}$/.test(arg)) | |
147 | return "[1,32] letters + hyphen/space"; | |
148 | return ""; | |
149 | }, | |
150 | ||
151 | "check_email": function(arg) | |
152 | { | |
153 | if (!_.isString(arg)) | |
154 | return "not a string"; | |
155 | if (arg.length > 64) | |
156 | return "string too long: max. 64 characters"; | |
157 | // Regexp used in "type='email'" inputs ( http://emailregex.com/ ) | |
158 | if (!/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(arg)) | |
159 | return "X@Y, alphanumerics and . _ - +(X)"; | |
160 | return ""; | |
161 | }, | |
162 | ||
163 | "check_code": function(arg) | |
164 | { | |
165 | if (!_.isString(arg)) | |
166 | return "not a string"; | |
167 | if (!/^[\w.-]{1,16}$/.test(arg)) | |
168 | return "[1,16] alphanumerics and . _ -"; | |
169 | return ""; | |
170 | }, | |
171 | ||
172 | "check_number": function(arg) | |
173 | { | |
174 | if (!_.isNumber(arg)) | |
175 | arg = parseFloat(arg); | |
176 | if (isNaN(arg)) | |
177 | return "not a number"; | |
178 | return ""; | |
179 | }, | |
180 | ||
181 | "check_integer": function(arg) | |
182 | { | |
183 | if (!_.isNumber(arg)) | |
184 | arg = parseInt(arg); | |
185 | if (isNaN(arg) || arg % 1 != 0) | |
186 | return "not an integer"; | |
187 | return ""; | |
188 | }, | |
189 | ||
190 | "check_positiveInteger": function(arg) | |
191 | { | |
192 | return Validator["check_integer"](arg) || (arg<0 ? "not positive" : ""); | |
193 | }, | |
194 | ||
195 | "check_boolean": function(arg) | |
196 | { | |
197 | if (!_.isBoolean(arg)) | |
198 | return "not a boolean"; | |
199 | return ""; | |
200 | }, | |
201 | ||
202 | "check_password": function(arg) | |
203 | { | |
204 | if (!_.isString(arg)) | |
205 | return "not a string"; | |
206 | if (!/^[\x21-\x7E]{1,16}$/.test(arg)) | |
207 | return "[1,16] ASCII characters with code in [33,126]"; | |
208 | return ""; | |
209 | }, | |
210 | ||
211 | // Sha-1 hash: length 40, hexadecimal | |
212 | "check_hash": function(arg) | |
213 | { | |
214 | if (!_.isString(arg)) | |
215 | return "not a string"; | |
216 | if (!/^[a-f0-9]{40}$/.test(arg)) | |
217 | return "not a sha-1 hash"; | |
218 | return ""; | |
219 | }, | |
220 | ||
221 | "check_integerArray": function(arg) | |
222 | { | |
223 | if (!_.isArray(arg)) | |
224 | return "not an array"; | |
225 | for (let i=0; i<arg.length; i++) | |
226 | { | |
227 | let error = Validator["check_integer"](arg[i]); | |
228 | if (error.length > 0) | |
229 | return error; | |
230 | } | |
231 | return ""; | |
232 | }, | |
233 | ||
234 | "check_stringOrIntegerArray": function(arg) | |
235 | { | |
236 | if (!_.isString(arg)) | |
237 | return Validator["check_integerArray"](arg); | |
238 | return ""; | |
239 | }, | |
240 | }); | |
241 | ||
242 | try { module.exports = Validator.checkObject; } catch (err) {} //for server |