-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathserver.js
289 lines (255 loc) · 10.2 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
const express = require('express'); // Import Express
const app = express(); // Instantiate Express
/*****************************************
* REGULAR (non-middleware) DEPENDENCIES *
*****************************************/
const moment = require('moment'); // Date parsing library
const mysql = require('mysql'); // Can create connections to MySQL
// Set up database connection
const db = mysql.createConnection({
host: process.env.DB_HOST,
port: 3306,
user: process.env.DB_USER, // Environment variable. Start app like: 'DB_USER=app DB_PASS=test nodemond index.js' OR use .env
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
const bcrypt = require('bcryptjs');
const sgMail = require('@sendgrid/mail');
/*******************************************
* IMPORT MIDDLEWARE AND EXPRESS HELPERS *
*******************************************/
const session = require('express-session'); // Used to create, set, and update cookies to maintain user sessions
const bodyParser = require('body-parser'); // Used to parse incoming POSTed data
const exphbs = require('express-handlebars'); // Templating engine
// Set up handlebars with a custom simple date formatting helper
const hbs = exphbs.create({
helpers: {
formatDate: function (date) {
return moment(date).format('MMM DD, YYYY');
}
}
})
const logger = require('./middleware/logger');
const passport = require('passport'); // Authentication middleware
const LocalStrategy = require('passport-local').Strategy;
const flash = require('express-flash');
/************************
* REGISTER MIDDLEWARE *
*************************/
app.use(logger.log); // Log all the things
// Initialize and configure Express sessions
// These settings are OK for us
app.use(session({
secret: 'ha8hWp,yoZF', // random characters for secret
cookie: { maxAge: 60000 }, // cookie expires after some time
saveUninitialized: true,
resave: true
}))
app.use(flash()); // Allow messages to be saved in req object for use in templates when rendering
app.use(bodyParser.urlencoded({ extended: false })); // Parse form submissions
app.use(bodyParser.json()); // parse application/json
app.use(express.static('public')); // Static files will use the 'public' folder as their root
app.engine('handlebars', hbs.engine); // Register the handlebars templating engine
app.set('view engine', 'handlebars'); // Set handlebars as our default template engine
/************************
* PASSPORT CONFIG *
*************************/
app.use(passport.initialize()); // Needed to use Passport at all
app.use(passport.session()); // Needed to allow for persistent sessions with passport
// Configure authentication using username and password
// In all callback functions that we use with passport we will expect a last argument, 'done'
// 'done' is analagous to 'next' in middleware (and of course we could name it 'next')
passport.use(new LocalStrategy({
passReqToCallback: true // Passes req to the callback function, so we can put messages there if needed
},
function (req, username, password, done) {
// Find the user based off their username
const q = `SELECT * FROM users WHERE username = ?;`
db.query(q, [username], function (err, results, fields) {
if (err) return done(err);
// User, if it exists, will be the first row returned
// There should also only _be_ one row, provided usernames are unique in the app (and they should be!)
const user = results[0]
// 'done' here is looking for the following arguments: error, user, and a message or callback
if (!user) {
return done(null, false, req.flash('loginMessage', 'User not found')); // req.flash stores a temporary key/value
}
// User exists, check password against hash
const userHash = user.hash; // Grab the hash of the user
// Hash and compare the provided password with the stored hash.
// This is an async function, so we have to use a callback to receive the results and continue
bcrypt.compare(password, userHash, function(err, matches) {
if (!matches) {
return done(null, false, req.flash('loginMessage', 'Incorrect username and/or password'));
}
// Otherwise, they match -- success! -- send passport the user (see: serializeUser)
return done(null, user);
});
})
}
))
// Tells passport what information to include in the session
// This will be run after authentication
// Just need ID for lookup later
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// Tells passport how to get user from information in session
// This will run on every request for which session data exists in a cookie.
passport.deserializeUser(function(id, done) {
const q = `SELECT * FROM users WHERE id = ?;`
db.query(q, [id], function (err, results, fields) {
done(err, results[0]) // results[0] will be stored _in req.user_ for use in later middleware
});
})
/************************
* ROUTES *
*************************/
// Homepage
app.get('/', function (req, res) {
const q = `SELECT * FROM posts`;
db.query(q, function (err, results, fields) {
if (err) {
console.error(err);
}
const templateData = {
articles: results
};
res.render('homepage', templateData);
});
});
// Individual blog post
app.get('/blog/post/:postid', function (req, res) {
const postId = req.params.postid;
const q = `SELECT * FROM posts WHERE id = ?`; // Fill in the blanks style escaping
db.query(q, [postId], function (err, results, fields) {
if (err) {
console.error(err);
}
const templateData = {
article: results[0]
}
res.render('singlePost', templateData);
});
});
//
// ACCOUNT MANAGEMENT
//
app.get('/login', function (req, res) {
const user = req.user;
if (user) {
// If we already have a user, don't let them see the login page, just send them to the admin!
res.redirect('/admin');
} else {
res.render('login', { loginMessage: req.flash('loginMessage') })
}
});
app.post('/login',
// In this case, invoke the local authentication strategy.
passport.authenticate('local', {
successRedirect: '/admin',
failureRedirect: '/login',
failureFlash: true
})
);
app.get('/register', function (req, res) {
const user = req.user;
if (user) {
res.redirect('/admin');
} else {
res.render('register', { registerMessage: req.flash('registerMessage') })
}
});
app.post('/register', function (req, res) {
const username = req.body.username;
const pass = req.body.password;
if (!username || !pass) {
req.flash('registerMessage', 'Username and password are required.')
return res.redirect('/register');
}
// Check if user exists, first
const checkExists = `SELECT * FROM users WHERE username = ?`
db.query(checkExists, [username], function (err, results, fields) {
if (err) {
console.error(err);
return res.status(500).send('Something bad happened...'); // Important: Don't execute other code
}
if (results[0]) {
req.flash('registerMessage', 'That username is already taken.');
return res.redirect('/register');
}
// Otherwise, user doesn't exist yet, let's create them!
// Generate salt and pass for the user
bcrypt.genSalt(10, function (err, salt) {
if (err) throw err;
bcrypt.hash(pass, salt, function (err, hash) {
if (err) throw err;
// Add user to database with username and hash
const q = `INSERT INTO users(id, username, hash) VALUES (null, ?, ?)`;
db.query(q, [username, hash], function (err, results, fields) {
if (err) console.error(err);
req.flash('registerMessage', 'Account created successfully.');
res.redirect('/register');
})
})
});
})
});
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/');
});
//
// Logged In Functionality
//
// All arguments after the route path ('/admin') are middleware – we can actually have multiple defined for one route!
app.get('/admin', requireLoggedIn, function (req, res) {
const user = req.user;
res.render('admin', { user: user, adminMessage: req.flash.adminMessage } )
});
// Add new post
app.post('/article', requireLoggedIn, function (req, res) {
// One style of escaping
const title = req.body.title;
const summary = req.body.summary;
const fulltext = req.body.full_text;
const image = req.body.image;
const q = `INSERT INTO posts(id, title, time, summary, full_text, image, author) VALUES (null, ?, NOW(), ?, ?, ?, ?)`
db.query(q, [title, summary, fulltext, image, req.user.id], function (err, results, fields) {
if (err) {
console.error(err);
return res.status(500).send('Failed. Oops.');
} else {
req.flash('adminMessage', 'Post added successfully!');
return res.redirect('/admin');
}
})
});
function requireLoggedIn(req, res, next) {
const user = req.user;
if (!user) {
return res.status(401).redirect('/login')
}
next();
}
// SENDGRID TEST
app.get('/mailtest', function (req, res) {
// sgMail.setApiKey(process.env.SENDGRID_API_KEY);
// const msg = {
// to: 'yaviner@gmail.com',
// from: 'yaviner@teachingfoodblog.com',
// subject: 'Sending with SendGrid is Fun',
// text: 'and easy to do anywhere, even with Node.js',
// html: '<strong>and easy to do anywhere, even with Node.js</strong>',
// };
// sgMail.send(msg);
res.send('Nope, you can\'t send email.')
})
// 404 handler
app.use(function (req, res, next) {
res.status(404).send("Sorry can't find that!");
});
// Listen in on a port to handle requests
const listener = app.listen(process.env.PORT || 5000, function () {
console.log(`BLOG APP listening on port ${listener.address().port}`);
});