aa39ebeba0f9544afaef30815de1eecf4c0a56f0
[qomet.git] / public / javascripts / utils / validation.js
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",
29 "discoTime": "positiveInteger",
30 "discoCount": "positiveInteger",
31 "totalDisco": "positiveInteger",
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