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