First commit
[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 "password": "password",
30 };
31
32 Validator.Assessment = {
33 "_id": "bson",
34 "cid": "bson",
35 "name": "code",
36 "mode": "alphanumeric", //"open" or "exam", but alphanumeric is good enough
37 "active": "boolean",
38 "fixed": "boolean",
39 "display": "alphanumeric", //"one" or "all"
40 "time": "integer",
41 "introduction": "string",
42 "conclusion": "string",
43 "coefficient": "number",
44 "questions": Validator.Question,
45 "papers": Validator.Paper,
46 };
47
48 Validator.User = {
49 "_id": "bson",
50 "email": "email",
51 "forename": "name",
52 "name": "name",
53 "initials": "unchecked", //not a user input
54 "loginToken": "unchecked",
55 "sessionTokens": "unchecked",
56 "token": "alphanumeric", //exception: for the purpose of user registration
57 };
58
59 Validator.Student = {
60 "number": "code",
61 "forename": "name",
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