-
Notifications
You must be signed in to change notification settings - Fork 32
/
server.js
320 lines (282 loc) · 10.1 KB
/
server.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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// See LICENSE.MD for license information.
'use strict';
/********************************
Dependencies
********************************/
// server middleware
const express = require('express');
// Authentication framework
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
// MongoDB connection library
const mongoose = require('mongoose');
const User = require('./server/models/user.model');
/********************************
Load environment
********************************/
require('dotenv').config();// Loads .env file into environment
/********************************
MongoDB Connection
********************************/
async function initializeDatabase() {
//Detects environment and connects to appropriate DB
var caCertificateBase64, mongoDbUrl;
caCertificateBase64 = process.env.CERTIFICATE_BASE64;
mongoDbUrl = process.env.MONGODB_URL;
function unicodeToChar(text) {
return text.replace(/\\u[\dA-F]{4}/gi,
function (match) {
return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
});
}
mongoDbUrl = unicodeToChar(mongoDbUrl);
console.log(`Connecting to database located at ${mongoDbUrl}...`);
// write down the certificate so that it can be used by MongoDB client
require('fs').writeFileSync('__temp__ca.pem', Buffer.from(caCertificateBase64, 'base64'));
var mongoDbOptions = {
useNewUrlParser: true,
ssl: true,
sslValidate: true,
sslCA: '__temp__ca.pem',
useUnifiedTopology: true
};
try {
await mongoose.connect(mongoDbUrl, mongoDbOptions);
console.log("Connected to database.");
} catch (err) {
console.error("Could not connect", err);
}
}
/********************************
Passport Middleware Configuration
********************************/
function configurePassport() {
console.log("Configuring passport authentication...");
// configure passport
passport.use(new LocalStrategy(
function (username, password, done) {
console.log(`Finding user with login ${username}`);
User.findOne({ username: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
// validatePassword method defined in user.model.js
if (!user.validatePassword(password, user.password)) {
return done(null, false, { message: 'Incorrect password.' });
}
console.log('User found!', user);
return done(null, user);
});
}
));
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
}
/********************************
Express Settings
********************************/
function configureApp() {
console.log("Setting up app middleware...");
const bodyParser = require('body-parser'); // parse HTTP requests
const cookieParser = require('cookie-parser');
const session = require('express-session');
const MongoStore = require('connect-mongo'); // store sessions in MongoDB for persistence
const app = express();
app.enable('trust proxy');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
secret: process.env.SESSION_SECRET || 'this_is_a_default_session_secret_in_case_one_is_not_defined',
resave: true,
store: new MongoStore({ client: mongoose.connection.getClient() }),
saveUninitialized: false,
cookie: { secure: false }
}));
app.use(passport.initialize());
app.use(passport.session());
return app;
}
/********************************
Routing
********************************/
function setupRoutes(app) {
console.log("Setting up app routes...");
// validation tool for processing user input
const expressValidator = require('express-validator');
const bcrypt = require('bcrypt');
// html, css, js
app.use(express.static(__dirname + '/public'));
// Account login
app.post('/account/login',
// Validation prior to checking DB. Front end validation exists, but this functions as a fail-safe
expressValidator.body('username', 'Username is required').notEmpty(),
expressValidator.body('password', 'Password is required').notEmpty(),
function (req, res) {
var errors = expressValidator.validationResult(req); // returns an object with results of validation check
if (errors.length > 0) {
res.status(401).send('Username or password was left empty. Please complete both fields and re-submit.');
return;
}
// Create session if username exists and password is correct
passport.authenticate('local', function (err, user) {
if (err) { return next(err); }
if (!user) { return res.status(401).send('User not found. Please check your entry and try again.'); }
req.login(user, function (err) { // creates session
if (err) { return res.status(500).send('Error saving session.'); }
var userInfo = {
username: user.username,
name: user.name,
email: user.email
};
return res.json(userInfo);
});
})(req, res);
});
// Account creation
app.post('/account/create',
// 1. Input validation. Front end validation exists, but this functions as a fail-safe
expressValidator.body('username', 'Username is required').notEmpty(),
expressValidator.body('password', 'Password is required').notEmpty(),
expressValidator.body('name', 'Name is required').notEmpty(),
expressValidator.body('email', 'Email is required and must be in a valid form').notEmpty().isEmail(),
function (req, res) {
var errors = expressValidator.validationResult(req); // returns an array with results of validation check
if (errors.length > 0) {
res.status(400).send(errors);
return;
}
// 2. Hash user's password for safe-keeping in DB
const salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(req.body.password, salt);
// 3. Create new object that store's new user data
var user = new User({
username: req.body.username,
password: hash,
email: req.body.email,
name: req.body.name
});
// 4. Store the data in MongoDB
User.findOne({ username: req.body.username }, function (err, existingUser) {
if (existingUser) {
return res.status(400).send('That username already exists. Please try a different username.');
}
user.save(function (err) {
if (err) {
console.log(err);
res.status(500).send('Error saving new account (database error). Please try again.');
return;
}
res.status(200).send('Account created! Please login with your new account.');
});
});
});
//Account deletion
app.post('/account/delete', authorizeRequest, function (req, res) {
User.deleteOne({ username: req.body.username }, function (err) {
if (err) {
console.log(err);
res.status(500).send('Error deleting account.');
return;
}
req.session.destroy(function (err) {
if (err) {
res.status(500).send('Error deleting account.');
console.log("Error deleting session: " + err);
return;
}
res.status(200).send('Account successfully deleted.');
});
});
});
// Account update
app.post('/account/update',
authorizeRequest,
// 1. Input validation. Front end validation exists, but this functions as a fail-safe
expressValidator.body('username', 'Username is required').notEmpty(),
expressValidator.body('password', 'Password is required').notEmpty(),
expressValidator.body('name', 'Name is required').notEmpty(),
expressValidator.body('email', 'Email is required and must be in a valid form').notEmpty().isEmail(),
function (req, res) {
var errors = expressValidator.validationResult(req); // returns an array with results of validation check
if (errors.length > 0) {
res.status(400).send(errors);
return;
}
// 2. Hash user's password for safe-keeping in DB
var salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(req.body.password, salt);
// 3. Store updated data in MongoDB
User.findOne({ username: req.body.username }, function (err, user) {
if (err) {
console.log(err);
return res.status(400).send('Error updating account.');
}
user.username = req.body.username;
user.password = hash;
user.email = req.body.email;
user.name = req.body.name;
user.save(function (err) {
if (err) {
console.log(err);
res.status(500).send('Error updating account.');
return;
}
res.status(200).send('Account updated.');
});
});
});
// // Account logout
app.get('/account/logout', function (req, res) {
// Destroys user's session
if (!req.user)
res.status(400).send('User not logged in.');
else {
req.session.destroy(function (err) {
if (err) {
res.status(500).send('Sorry. Server error in logout process.');
console.log("Error destroying session: " + err);
return;
}
res.status(200).send('Success logging user out!');
});
}
});
// Custom middleware to check if user is logged-in
function authorizeRequest(req, res, next) {
if (req.user) {
next();
} else {
res.status(401).send('Unauthorized. Please login.');
}
}
// Protected route requiring authorization to access.
app.get('/protected', authorizeRequest, function (req, res) {
res.send("This is a protected route only visible to authenticated users.");
});
}
/********************************
Ports
********************************/
function startApp(app) {
app.listen(process.env.PORT, process.env.BIND, function () {
console.log(`Application running at http://localhost:${process.env.PORT}`);
});
}
async function main() {
await initializeDatabase();
configurePassport();
const app = configureApp();
setupRoutes(app);
startApp(app);
}
main();