Sanitize inputs on server side
[vchess.git] / server / models / User.js
1 var db = require("../utils/database");
2 var genToken = require("../utils/tokenGenerator");
3 var params = require("../config/parameters");
4 var sendEmail = require('../utils/mailer');
5
6 /*
7 * Structure:
8 * _id: integer
9 * name: varchar
10 * email: varchar
11 * loginToken: token on server only
12 * loginTime: datetime (validity)
13 * sessionToken: token in cookies for authentication
14 * notify: boolean (send email notifications for corr games)
15 * created: datetime
16 */
17
18 const UserModel =
19 {
20 checkNameEmail: function(o)
21 {
22 if (typeof o.name === "string")
23 {
24 if (o.name.length == 0)
25 return "Empty name";
26 if (!o.name.match(/^[\w]+$/))
27 return "Bad characters in name";
28 }
29 if (typeof o.email === "string")
30 {
31 if (o.email.length == 0)
32 return "Empty email";
33 if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
34 return "Bad characters in email";
35 }
36 return ""; //NOTE: not required, but more consistent... (?!)
37 },
38
39 // NOTE: parameters are already cleaned (in controller), thus no sanitization here
40 create: function(name, email, notify, callback)
41 {
42 db.serialize(function() {
43 const insertQuery =
44 "INSERT INTO Users " +
45 "(name, email, notify, created) VALUES " +
46 "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
47 db.run(insertQuery, err => {
48 if (!!err)
49 return callback(err);
50 db.get("SELECT last_insert_rowid() AS rowid", callback);
51 });
52 });
53 },
54
55 // Find one user (by id, name, email, or token)
56 getOne: function(by, value, cb)
57 {
58 const delimiter = (typeof value === "string" ? "'" : "");
59 db.serialize(function() {
60 const query =
61 "SELECT * " +
62 "FROM Users " +
63 "WHERE " + by + " = " + delimiter + value + delimiter;
64 db.get(query, cb);
65 });
66 },
67
68 getByIds: function(ids, cb) {
69 db.serialize(function() {
70 const query =
71 "SELECT id, name " +
72 "FROM Users " +
73 "WHERE id IN (" + ids + ")";
74 db.all(query, cb);
75 });
76 },
77
78 /////////
79 // MODIFY
80
81 setLoginToken: function(token, uid, cb)
82 {
83 db.serialize(function() {
84 const query =
85 "UPDATE Users " +
86 "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
87 "WHERE id = " + uid;
88 db.run(query, cb);
89 });
90 },
91
92 // Set session token only if empty (first login)
93 // NOTE: weaker security (but avoid to re-login everywhere after each logout)
94 // TODO: option would be to reset all tokens periodically, e.g. every 3 months
95 trySetSessionToken: function(uid, cb)
96 {
97 // Also empty the login token to invalidate future attempts
98 db.serialize(function() {
99 const querySessionToken =
100 "SELECT sessionToken " +
101 "FROM Users " +
102 "WHERE id = " + uid;
103 db.get(querySessionToken, (err,ret) => {
104 if (!!err)
105 return cb(err);
106 const token = ret.sessionToken || genToken(params.token.length);
107 const queryUpdate =
108 "UPDATE Users " +
109 "SET loginToken = NULL" +
110 (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
111 "WHERE id = " + uid;
112 db.run(queryUpdate);
113 cb(null, token);
114 });
115 });
116 },
117
118 updateSettings: function(user, cb)
119 {
120 db.serialize(function() {
121 const query =
122 "UPDATE Users " +
123 "SET name = '" + user.name + "'" +
124 ", email = '" + user.email + "'" +
125 ", notify = " + user.notify + " " +
126 "WHERE id = " + user.id;
127 db.run(query, cb);
128 });
129 },
130
131 /////////////////
132 // NOTIFICATIONS
133
134 tryNotify: function(oppId, message)
135 {
136 UserModel.getOne("id", oppId, (err,opp) => {
137 if (!err || !opp.notify)
138 return; //error is ignored here (TODO: should be logged)
139 const subject = "vchess.club - notification";
140 const body = "Hello " + opp.name + "!\n" + message;
141 sendEmail(params.mail.noreply, opp.email, subject, body, err => {
142 res.json(err || {});
143 });
144 });
145 },
146
147 ////////////
148 // CLEANING
149
150 cleanUsersDb: function()
151 {
152 const tsNow = Date.now();
153 // 86400000 = 24 hours in milliseconds
154 const day = 86400000;
155 db.serialize(function() {
156 const query =
157 "SELECT id, sessionToken, created " +
158 "FROM Users";
159 db.all(query, (err, users) => {
160 users.forEach(u => {
161 // Remove unlogged users for >1 day
162 if (!u.sessionToken && tsNow - u.created > day)
163 db.run("DELETE FROM Users WHERE id = " + u.id);
164 });
165 });
166 });
167 },
168 }
169
170 module.exports = UserModel;