-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.js
244 lines (226 loc) · 7.61 KB
/
app.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
// Configure any dotenv defined variables
require('dotenv').config();
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const SequelizeStore = require('connect-session-sequelize')(session.Store);
const csurf = require('csurf');
const helmet = require('helmet');
const favicon = require('serve-favicon');
// Load models directory (which loads ./models/index)
const models = require('./models');
const indexRouter = require('./routes/index');
const eventRouter = require('./routes/event');
const memberRouter = require('./routes/member');
const app = express();
// add helmet protections from various attacks
app.use(
helmet.contentSecurityPolicy({
useDefaults: true,
directives: {
'script-src': ['"self"', 'https://drive.google.com/', 'https://docs.google.com/', 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js', 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js', 'https://code.jquery.com/jquery-3.3.1.slim.min.js'],
'script-src-elem': ['"self"', 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js', 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js'],
'frame-src': ['"self"', 'https://formfacade.com/', 'https://www.google.com/', 'https://drive.google.com/', 'https://docs.google.com/', 'https://calendar.google.com/', 'https://mapsengine.google.com/'],
},
}),
);
app.use(helmet.dnsPrefetchControl());
app.use(helmet.expectCt());
app.use(helmet.frameguard());
app.use(helmet.hidePoweredBy());
app.use(helmet.hsts());
app.use(helmet.ieNoOpen());
app.use(helmet.noSniff());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy());
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.ico')));
// Don't log page requests in tests
if (process.env.NODE_ENV !== 'test') {
app.use(logger('dev'));
}
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/**
* Session configuration
*/
const sequelizeSessionStore = new SequelizeStore({
db: models.sequelize,
table: 'Session',
});
// if cookie secret isn't defined - throw an error
if (!process.env.COOKIE_SECRET) {
throw Error('COOKIE_SECRET environment variable is undefined');
}
const sess = {
secret: process.env.COOKIE_SECRET,
store: sequelizeSessionStore,
resave: false,
cookie: {},
saveUninitialized: true,
};
// In production, serve secure cookies (https only)
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1); // trust first proxy
sess.cookie.secure = true; // serve secure cookies
}
app.use(session({
secret: process.env.COOKIE_SECRET,
store: sequelizeSessionStore,
resave: false,
cookie: {},
saveUninitialized: true,
}));
// cross site request forgery protection
// disable csurf for testing
if (process.env.NODE_ENV === 'test') {
app.use(csurf({ cookie: true, ignoreMethods: ['GET', 'POST'] }));
} else {
app.use(csurf({ cookie: true }));
}
app.use(passport.initialize());
app.use(passport.session());
/**
* Database configuration
* The actual connecting to the database happend in `./models/index.js`,
* this file just syncs the models and verifies the connection is valid
*/
// Sync models - very important step
models.sequelize.sync();
// Check connection
models.sequelize.authenticate().then(() => {
console.log('Connection to database established.');
}).catch((err) => {
console.error('Unable to connect to the database:', err);
// Kill the process because there's no connection to the database
process.exit();
});
/**
* custom variable definiton middleware
* This defines variables for alerting if they do not exist
* Defines status variable if it does not exist
*/
function createVariablesMiddleware(req, res, next) {
if (typeof req.session.status !== 'undefined') {
res.locals.status = req.session.status;
} else {
res.locals.status = 200;
}
req.session.status = 200;
if (typeof req.session.alert !== 'undefined') {
// If the alert object exists, set it to the local object so it will render
res.locals.alert = req.session.alert;
} else {
// Regardless of if alert exists, set it to an empty object
res.locals.alert = {};
// Set all arrays to empty
res.locals.alert.errorMessages = [];
res.locals.alert.infoMessages = [];
res.locals.alert.successMessages = [];
}
// Regardless of if alert exists, set it to an empty object
req.session.alert = {};
// Set all arrays to empty
req.session.alert.errorMessages = [];
req.session.alert.infoMessages = [];
req.session.alert.successMessages = [];
// set res.locals variables so that the views have access to them
res.locals.user = req.user;
// set csrfToken to token
res.locals.csrfToken = req.csrfToken();
// continue execution to next middleware handler
next();
}
app.use(createVariablesMiddleware);
/**
* define routes
*/
app.use('/', indexRouter);
app.use('/event', eventRouter);
app.use('/member', memberRouter);
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});
/**
* Error Handler
*
* This is the error handler that is always next in the stack for synchronous errors
* If there is a synchronous error in code, this important handler will catch it and tell the user
* that something went wrong
* If the code is asynchronous, this must be called with next(err) or similar
*
* This function doesn't work without 'next' as an argument
*/
app.use((err, req, res, next) => { /* eslint-disable-line no-unused-vars */
// don't need to log the whole stack error for 404's
if (err.status && err.status !== 404) {
// let's log the error so we have it in the logs
console.error(err);
}
// destructure message from err
const { message } = err;
let error = null;
// only provide error in development
if (process.env.NODE_ENV !== 'production') {
error = err;
}
// render the error page
// will return status if defined on err, otherwise a 500
res.status(err.status || 500);
return res.render('error', {
error,
message,
});
});
/**
* Passport configuration
*
*/
passport.use(new LocalStrategy(
{
usernameField: 'email',
},
(username, password, done) => {
models.Member.findOne({
where: {
email: username,
},
}).then((member) => {
if (member) {
// Member exists, validate password
return models.Member.comparePassword(password, member).then((res) => {
if (res === false) {
return done(null, false, { message: 'Incorrect password.' });
}
// Member is found and has a valid password
return done(null, member);
});
}
// Member doesn't exist, throw an error
return done(null, false, { message: 'Member not found.' });
});
},
));
// takes the user(Member) and converts it to just an id for the client session cookie
passport.serializeUser((user, done) => {
done(null, user.id);
});
// converts the cookie from the client into an instance of Member upon a request
passport.deserializeUser((id, done) => {
models.Member.findByPk(id).then((member) => {
return done(null, member);
}).catch((err) => {
return done(err, null);
});
});
module.exports = app;