-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathsession.js
271 lines (234 loc) · 9.81 KB
/
session.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
const UserDAO = require("../data/user-dao").UserDAO;
const AllocationsDAO = require("../data/allocations-dao").AllocationsDAO;
const {
environmentalScripts
} = require("../../config/config");
/* The SessionHandler must be constructed with a connected db */
function SessionHandler(db) {
"use strict";
const userDAO = new UserDAO(db);
const allocationsDAO = new AllocationsDAO(db);
const prepareUserData = (user, next) => {
// Generate random allocations
const stocks = Math.floor((Math.random() * 40) + 1);
const funds = Math.floor((Math.random() * 40) + 1);
const bonds = 100 - (stocks + funds);
allocationsDAO.update(user._id, stocks, funds, bonds, (err) => {
if (err) return next(err);
});
};
this.isAdminUserMiddleware = (req, res, next) => {
if (req.session.userId) {
return userDAO.getUserById(req.session.userId, (err, user) => user && user.isAdmin ? next() : res.redirect("/login"));
}
console.log("redirecting to login");
return res.redirect("/login");
};
this.isLoggedInMiddleware = (req, res, next) => {
if (req.session.userId) {
return next();
}
console.log("redirecting to login");
return res.redirect("/login");
};
this.displayLoginPage = (req, res, next) => {
return res.render("login", {
userName: "",
password: "",
loginError: "",
environmentalScripts
});
};
this.handleLoginRequest = (req, res, next) => {
const {
userName,
password
} = req.body
userDAO.validateLogin(userName, password, (err, user) => {
const errorMessage = "Invalid username and/or password";
const invalidUserNameErrorMessage = "Invalid username";
const invalidPasswordErrorMessage = "Invalid password";
if (err) {
if (err.noSuchUser) {
console.log('Error: attempt to login with invalid user: ', userName);
// Fix for A1 - 3 Log Injection - encode/sanitize input for CRLF Injection
// that could result in log forging:
// - Step 1: Require a module that supports encoding
// const ESAPI = require('node-esapi');
// - Step 2: Encode the user input that will be logged in the correct context
// following are a few examples:
// console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForHTML(userName));
// console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForJavaScript(userName));
// console.log('Error: attempt to login with invalid user: %s', ESAPI.encoder().encodeForURL(userName));
// or if you know that this is a CRLF vulnerability you can target this specifically as follows:
// console.log('Error: attempt to login with invalid user: %s', userName.replace(/(\r\n|\r|\n)/g, '_'));
return res.render("login", {
userName: userName,
password: "",
loginError: invalidUserNameErrorMessage,
//Fix for A2-2 Broken Auth - Uses identical error for both username, password error
// loginError: errorMessage
environmentalScripts
});
} else if (err.invalidPassword) {
return res.render("login", {
userName: userName,
password: "",
loginError: invalidPasswordErrorMessage,
//Fix for A2-2 Broken Auth - Uses identical error for both username, password error
// loginError: errorMessage
environmentalScripts
});
} else {
return next(err);
}
}
// A2-Broken Authentication and Session Management
// Upon login, a security best practice with regards to cookies session management
// would be to regenerate the session id so that if an id was already created for
// a user on an insecure medium (i.e: non-HTTPS website or otherwise), or if an
// attacker was able to get their hands on the cookie id before the user logged-in,
// then the old session id will render useless as the logged-in user with new privileges
// holds a new session id now.
// Fix the problem by regenerating a session in each login
// by wrapping the below code as a function callback for the method req.session.regenerate()
// i.e:
// `req.session.regenerate(() => {})`
req.session.userId = user._id;
return res.redirect(user.isAdmin ? "/benefits" : "/dashboard")
});
};
this.displayLogoutPage = (req, res) => {
req.session.destroy(() => res.redirect("/"));
};
this.displaySignupPage = (req, res) => {
res.render("signup", {
userName: "",
password: "",
passwordError: "",
email: "",
userNameError: "",
emailError: "",
verifyError: "",
environmentalScripts
});
};
const validateSignup = (userName, firstName, lastName, password, verify, email, errors) => {
const USER_RE = /^.{1,20}$/;
const FNAME_RE = /^.{1,100}$/;
const LNAME_RE = /^.{1,100}$/;
const EMAIL_RE = /^[\S]+@[\S]+\.[\S]+$/;
const PASS_RE = /^.{1,20}$/;
/*
//Fix for A2-2 - Broken Authentication - requires stronger password
//(at least 8 characters with numbers and both lowercase and uppercase letters.)
const PASS_RE =/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
*/
errors.userNameError = "";
errors.firstNameError = "";
errors.lastNameError = "";
errors.passwordError = "";
errors.verifyError = "";
errors.emailError = "";
if (!USER_RE.test(userName)) {
errors.userNameError = "Invalid user name.";
return false;
}
if (!FNAME_RE.test(firstName)) {
errors.firstNameError = "Invalid first name.";
return false;
}
if (!LNAME_RE.test(lastName)) {
errors.lastNameError = "Invalid last name.";
return false;
}
if (!PASS_RE.test(password)) {
errors.passwordError = "Password must be 8 to 18 characters" +
" including numbers, lowercase and uppercase letters.";
return false;
}
if (password !== verify) {
errors.verifyError = "Password must match";
return false;
}
if (email !== "") {
if (!EMAIL_RE.test(email)) {
errors.emailError = "Invalid email address";
return false;
}
}
return true;
}
this.handleSignup = (req, res, next) => {
const {
email,
userName,
firstName,
lastName,
password,
verify
} = req.body;
// set these up in case we have an error case
const errors = {
"userName": userName,
"email": email
};
if (validateSignup(userName, firstName, lastName, password, verify, email, errors)) {
userDAO.getUserByUserName(userName, (err, user) => {
if (err) return next(err);
if (user) {
errors.userNameError = "User name already in use. Please choose another";
return res.render("signup", {
...errors,
environmentalScripts
});
}
userDAO.addUser(userName, firstName, lastName, password, email, (err, user) => {
if (err) return next(err);
//prepare data for the user
prepareUserData(user, next);
/*
sessionDAO.startSession(user._id, (err, sessionId) => {
if (err) return next(err);
res.cookie("session", sessionId);
req.session.userId = user._id;
return res.render("dashboard", { ...user, environmentalScripts });
});
*/
req.session.regenerate(() => {
req.session.userId = user._id;
// Set userId property. Required for left nav menu links
user.userId = user._id;
return res.render("dashboard", {
...user,
environmentalScripts
});
});
});
});
} else {
console.log("user did not validate");
return res.render("signup", {
...errors,
environmentalScripts
});
}
};
this.displayWelcomePage = (req, res, next) => {
let userId;
if (!req.session.userId) {
console.log("welcome: Unable to identify user...redirecting to login");
return res.redirect("/login");
}
userId = req.session.userId;
userDAO.getUserById(userId, (err, doc) => {
if (err) return next(err);
doc.userId = userId;
return res.render("dashboard", {
...doc,
environmentalScripts
});
});
};
}
module.exports = SessionHandler;