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", | |
e99c53fb BA |
45 | "coefficient": "number", |
46 | "questions": Validator.Question, | |
47 | "papers": Validator.Paper, | |
48 | }; | |
49 | ||
50 | Validator.User = { | |
51 | "_id": "bson", | |
52 | "email": "email", | |
e99c53fb BA |
53 | "name": "name", |
54 | "initials": "unchecked", //not a user input | |
55 | "loginToken": "unchecked", | |
56 | "sessionTokens": "unchecked", | |
57 | "token": "alphanumeric", //exception: for the purpose of user registration | |
58 | }; | |
59 | ||
60 | Validator.Student = { | |
61 | "number": "code", | |
e99c53fb BA |
62 | "name": "name", |
63 | "group": "positiveInteger", | |
64 | }; | |
65 | ||
66 | Validator.Course = { | |
67 | "_id": "bson", | |
68 | "uid": "bson", | |
69 | "code": "code", | |
70 | "description": "string", | |
71 | "password": "hash", | |
72 | "students": Validator.Student, | |
73 | }; | |
74 | ||
75 | Object.assign(Validator, | |
76 | { | |
77 | // Recurse into sub-documents | |
78 | checkObject_aux: function(obj, model) | |
79 | { | |
80 | for (let key of Object.keys(obj)) | |
81 | { | |
82 | if (!model[key]) | |
83 | return "Unknown field"; | |
84 | if (model[key] == "unchecked") //not a user input (ignored) | |
85 | continue; | |
86 | if (_.isObject(model[key])) | |
87 | { | |
88 | // TODO: next loop seems too heavy... (only a concern if big class import?) | |
89 | for (let item of obj[key]) | |
90 | { | |
91 | let error = Validator.checkObject_aux(item, model[key]); | |
92 | if (error.length > 0) | |
93 | return error; | |
94 | } | |
95 | } | |
96 | else | |
97 | { | |
98 | let error = Validator[ "check_" + model[key] ](obj[key]); | |
99 | if (error.length > 0) | |
100 | return key + ": " + error; | |
101 | } | |
102 | } | |
103 | return ""; | |
104 | }, | |
105 | ||
106 | // Always check top-level object | |
107 | checkObject: function(obj, name) | |
108 | { | |
109 | return Validator.checkObject_aux(obj, Validator[name]); | |
110 | }, | |
111 | ||
112 | "check_string": function(arg) | |
113 | { | |
114 | return ""; //strings are unchecked, but sanitized | |
115 | }, | |
116 | ||
117 | "check_section": function(arg) | |
118 | { | |
119 | if (!_.isString(arg)) | |
120 | return "not a string"; | |
121 | if (!/^[0-9.]+$/.test(arg)) | |
122 | return "digits and dot only"; | |
123 | return ""; | |
124 | }, | |
125 | ||
126 | "check_stringArray": function(arg) | |
127 | { | |
128 | return !_.isArray(arg) ? "not an array" : ""; | |
129 | }, | |
130 | ||
131 | "check_alphanumeric": function(arg) | |
132 | { | |
133 | return arg.match(/^[\w]{1,32}$/) === null ? "[1,32] alphanumerics" : ""; | |
134 | }, | |
135 | ||
136 | "check_bson": function(arg) | |
137 | { | |
138 | return arg.match(/^[a-z0-9]{24}$/) === null ? "not a BSON id" : ""; | |
139 | }, | |
140 | ||
141 | "check_name": function(arg) | |
142 | { | |
143 | if (!_.isString(arg)) | |
144 | return "not a string"; | |
145 | if (!/^[a-zA-Z\u00C0-\u024F -]{1,32}$/.test(arg)) | |
146 | return "[1,32] letters + hyphen/space"; | |
147 | return ""; | |
148 | }, | |
149 | ||
150 | "check_email": function(arg) | |
151 | { | |
152 | if (!_.isString(arg)) | |
153 | return "not a string"; | |
154 | if (arg.length > 64) | |
155 | return "string too long: max. 64 characters"; | |
156 | // Regexp used in "type='email'" inputs ( http://emailregex.com/ ) | |
157 | if (!/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(arg)) | |
158 | return "X@Y, alphanumerics and . _ - +(X)"; | |
159 | return ""; | |
160 | }, | |
161 | ||
162 | "check_code": function(arg) | |
163 | { | |
164 | if (!_.isString(arg)) | |
165 | return "not a string"; | |
166 | if (!/^[\w.-]{1,16}$/.test(arg)) | |
167 | return "[1,16] alphanumerics and . _ -"; | |
168 | return ""; | |
169 | }, | |
170 | ||
171 | "check_number": function(arg) | |
172 | { | |
173 | if (!_.isNumber(arg)) | |
174 | arg = parseFloat(arg); | |
175 | if (isNaN(arg)) | |
176 | return "not a number"; | |
177 | return ""; | |
178 | }, | |
179 | ||
180 | "check_integer": function(arg) | |
181 | { | |
182 | if (!_.isNumber(arg)) | |
183 | arg = parseInt(arg); | |
184 | if (isNaN(arg) || arg % 1 != 0) | |
185 | return "not an integer"; | |
186 | return ""; | |
187 | }, | |
188 | ||
189 | "check_positiveInteger": function(arg) | |
190 | { | |
191 | return Validator["check_integer"](arg) || (arg<0 ? "not positive" : ""); | |
192 | }, | |
193 | ||
194 | "check_boolean": function(arg) | |
195 | { | |
196 | if (!_.isBoolean(arg)) | |
197 | return "not a boolean"; | |
198 | return ""; | |
199 | }, | |
200 | ||
201 | "check_password": function(arg) | |
202 | { | |
203 | if (!_.isString(arg)) | |
204 | return "not a string"; | |
205 | if (!/^[\x21-\x7E]{1,16}$/.test(arg)) | |
206 | return "[1,16] ASCII characters with code in [33,126]"; | |
207 | return ""; | |
208 | }, | |
209 | ||
210 | // Sha-1 hash: length 40, hexadecimal | |
211 | "check_hash": function(arg) | |
212 | { | |
213 | if (!_.isString(arg)) | |
214 | return "not a string"; | |
215 | if (!/^[a-f0-9]{40}$/.test(arg)) | |
216 | return "not a sha-1 hash"; | |
217 | return ""; | |
218 | }, | |
219 | ||
220 | "check_integerArray": function(arg) | |
221 | { | |
222 | if (!_.isArray(arg)) | |
223 | return "not an array"; | |
224 | for (let i=0; i<arg.length; i++) | |
225 | { | |
226 | let error = Validator["check_integer"](arg[i]); | |
227 | if (error.length > 0) | |
228 | return error; | |
229 | } | |
230 | return ""; | |
231 | }, | |
232 | ||
233 | "check_stringOrIntegerArray": function(arg) | |
234 | { | |
235 | if (!_.isString(arg)) | |
236 | return Validator["check_integerArray"](arg); | |
237 | return ""; | |
238 | }, | |
239 | }); | |
240 | ||
241 | try { module.exports = Validator.checkObject; } catch (err) {} //for server |