-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexpress_server.js
705 lines (606 loc) · 22.3 KB
/
express_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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
//
// LHL Project - TinyApp
// https://flex-web.compass.lighthouselabs.ca/workbooks/flex-m03w6/activities/529?journey_step=36&workbook=9
// https://flex-web.compass.lighthouselabs.ca/projects/tiny-app
// 2022-08-03 -> 2022-08-05
//
//
// REQUIRES & INCLUDES
//
const { conColor,
myDateObject,
findUserByEmail,
cookiesButNoMilk,
getOpSys,
makeServerTitle,
urlExists,
tinyTrack,
clickTrack
} = require('./helpers.js');
const fs = require('fs'); // file services
const bcrypt = require("bcryptjs"); // password encryption
const express = require("express"); // express.js render engine
const cookieSession = require('cookie-session'); // cookies management - encrypted - see cookieName() for details
const methodOverride = require('method-override'); // express method override for CRUD
const app = express();
const PORT = 8080; // default port 8080
// const path = require('path');
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use(express.urlencoded({ extended: true }));
app.use(methodOverride('_method')); // override with POST having ?_method=DELETE
app.use(cookieSession({
name: 'tinyapp',
keys: ['this is a secret tinyapp key']
})); // SECURE COOKIES
//
// GLOBAL general variables necessary to CORE functionality
//
let uid = ""; // for cookie tracking across all functionality
//
// GLOBAL DATABASE variables
//
// urlDatabase - this is a working database for logged in users only
const urlDatabase = {
"b2xVn2": "http://www.lighthouselabs.ca",
"9sm5xK": "http://www.google.com"
};
// MAIN urlDatabase -- assigned with owner ID
const urlDatabaseMain = {
b6UTxQ: {
longURL: "https://www.tsn.ca", // sample data
userID: "aJ48lW",
},
i3BoGr: {
longURL: "https://www.google.ca", // sample data
userID: "aJ48lW",
},
};
const usersDatabase = {
// test user accounts will not work as we're using encrypted passwords now
userRandomID: {
id: "userRandomID",
email: "user@example.com",
password: "purple-monkey-dinosaur",
},
user2RandomID: {
id: "user2RandomID",
email: "user2@example.com",
password: "dishwasher-funk",
},
};
// analytics database: individual linkID and totalClicks
const trackingDatabase = {
// sample data on the sample items
"9sm5xK": {
lid: "9sm5xK",
totalClicks: 53,
},
"b2xVn2": {
lid: "b2xVn2",
totalClicks: 79,
},
};
// analytics database: complete click LOG
const clickDatabase = {
// structure example:
// dateStamp: {
// lid: 12345,
// uid: 23456,
// dateStamp: 12345,
// }
};
//
// SETUP ADDITIONAL HELPER FUNCTIONS:
// also see helpers.js for more
//
//
// consoleLog() replacement handler
// allows for -quiet mode toggles, -logfile for creating logfiles and DEBUG modes
// USAGE: consolelog(input text string, override, debug)
// where:
// override is TRUE then disregard quiet mode
// debug is TRUE then allow for DEBUG info logging (debug is a TODO item for final function)
// returns nothing when done
// - if argv is -logfile, then create a server log file with date stamps
// - if argv is -quiet, then only log if override is TRUE
const consolelog = function(inputText,override) {
let createFile = "no"; // default to no log file
for (let x = 0; x < process.argv.length; x ++) {
if (process.argv[x] === '-quiet' && override !== true) {
return;
}
if (process.argv[x] === '-logfile') {
createFile = "yes";
}
}
if (!inputText) { // no input text is to generate a blank line
console.log(' ');
return;
}
let logDate = myDateObject.justDate();
let logTime = myDateObject.justTime();
if (createFile === "yes") {
// NOTE TO CODE REVIEWER: below line is REQUIRED to remove escape characters before sending to a file!
// How can I avoid the ESLINT issue?
let strippedText = inputText.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); // clear escape codes
strippedText = strippedText.replace(/(\r\n|\n|\r)/gm, ""); // clear newlines (we'll use our own for server log)
const logfileText = '\r\n' + logDate + ' - ' + logTime + ' - ' + strippedText;
const logfileMaxSize = 50000;
fs.stat('tinyapp.log',(err,stats) => {
if (err) {
// TODO implement actual error checking
// do nothing if error - hopefully just file not exist
} else {
if (stats.size > logfileMaxSize) {
// TODO implement actual error checking
// delete the log file:
fs.unlink('tinyapp.log', function(err) {
if (err) {
// TODO - implement actual error checking
// do nothing - hopefully just file not exist
} else {
console.log("File removed:", 'tinyapp.log');
}
});
}
}
});
fs.appendFile('tinyapp.log', logfileText, function(err) {
if (err) throw err;
});
}
console.log(conColor.dim + logTime + ' - ' + conColor.reset + inputText); // all that above and we'll console.log the log entry
};
//
// create a random ID -default 6 chars longer, otherwise specify # of chars to create
// return is the random ID
//
const makeID = function(numChars) {
let yourCode = "";
let possibleChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
if (!numChars) {
numChars = 6; // set a default to 6 characters of a random ID
}
for (let i = 0; i < numChars; i++) {
yourCode += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length));
}
return yourCode;
};
//
// COOKIE function - cookieName (req, operation, cookieData);
// USAGE: operation is clear, set, read
// cookieData is the data to set in cookie
// req is express.js request object
// return is NULL for set or clear, or uid (user id) - a uid of "nobody" may be returned in some failed authentication situations
//
/* SETUP WITH:
const cookieSession = require('cookie-session'); // cookies management - secure
app.use(cookieSession({
name: 'tinyapp',
keys: ['this is a secret tinyapp key']
})); // SECURE COOKIES
reference: https://github.com/expressjs/cookie-session
*/
const cookieName = function(req,operation,cookieData) {
// DELETE any set cookies
if (operation === 'clear') {
req.session = null;
return;
}
// SET any cookie info
if (operation === 'set' && cookieData) {
req.session.euid = cookieData;
consolelog(`nom nom nom... we've got NEW cookies`);
return;
}
// READ any cookies - also DEFAULT operation if not set
if (operation === 'read' || !operation) {
uid = req.session.euid; // this is via SECURE COOKIES
if (!uid) {
uid = "nobody"; // use "nobody" as a monitoring system for bad logins
consolelog(`"${conColor.green}nobody${conColor.red}" is trying to access the system!${conColor.reset}\n`);
return uid;
}
consolelog(uid + " says " + conColor.green + cookiesButNoMilk() + conColor.reset);
return uid;
}
consolelog("BAD cookie data in cookieName()");
};
//
// SECURITY function - validateUser
// lookup userID in database and compare password to suppliedPassword
// returns the userID if validated, otherwise return null
//
const validateUser = function(userID, suppliedPassword) {
if (bcrypt.compareSync(suppliedPassword,usersDatabase[userID].password)) { // bcrypt.compareSync returns true or false
consolelog(userID + ` entered ${conColor.red}correct password!${conColor.reset}`);
return userID;
} else {
consolelog(userID + ` Password didn't match! ${conColor.green}Hopefully it's not a hacker at our door!${conColor.reset}`);
return null;
}
};
//
// check userDatabase for userID - return TRUE if found, FALSE if not
//
const isActualUser = function(userID) {
for (let users in usersDatabase) {
if (usersDatabase[users]["id"] === userID) {
return true;
}
}
return false;
};
//
// Count userid in inputObject then
// return an object with counts - used for analytics - unique click thrus
//
const countUIDS = function(inputObject) {
let returnObject = {}, tempObject = {};
for (let item in inputObject) {
let tuid = inputObject[item].uid;
if (!returnObject[tuid]) { // if it's new, start count at 1
tempObject = {
count: 1,
};
returnObject[tuid] = tempObject;
} else {
returnObject[tuid].count += 1; // inc the count since it exists already
}
}
return (returnObject);
};
//
// urlsForUser(id)
// input user id field and read MAIN database and update urlDatabase to be ONLY the id's that match to current user.
//
const urlsForUser = function(id) {
if (!id) {
id = uid;
}
consolelog("Building current user database...");
// delete the existing user URL database
for (const prop of Object.getOwnPropertyNames(urlDatabase)) {
delete urlDatabase[prop];
}
// rebuild the user URL database with 'owned' tiny URLs
for (let dbItem in urlDatabaseMain) {
// match uid to maindb.id.uid - if so, populate urlDatabase
// dbuid = urlDatabaseMain[db].replace("\"",'');
if (urlDatabaseMain[dbItem].userID === uid) {
urlDatabase[dbItem] = urlDatabaseMain[dbItem].longURL;
}
}
};
//
// hackCheck(linkID)
// returns true if security check works out, otherwise false if hack attempt
// pass main database tinyURL link id, pull user from record & compare to logged in user.
//
const hackCheck = function(linkID) {
consolelog(`running hack check on link ${linkID} and user ${uid}`);
if (uid === urlDatabaseMain[linkID].userID) {
return true;
} else {
consolelog(`HACK ATTEMPT - user ${uid} tried to modify tiny URL owned by ${urlDatabaseMain[linkID].userID}`);
return false;
}
};
/***
* _______ _______ _______ ______ _______
* | || || _ || _ | | |
* | _____||_ _|| |_| || | || |_ _|
* | |_____ | | | || |_||_ | |
* |_____ | | | | || __ | | |
* _____| | | | | _ || | | | | |
* |_______| |___| |__| |__||___| |_| |___|
*/
makeServerTitle();
//
// setup the listener
//
app.listen(PORT, () => {
console.log(` ${conColor.green}TinyApp server is now listening on port ${conColor.orange}${PORT}${conColor.green}!${conColor.reset}`);
if (process.argv[2] === '-quiet') {
consolelog(` ${conColor.orange}${conColor.dim}(( Server is running in silent mode. ))${conColor.reset}`,true);
}
consolelog(` ${conColor.dim}(Don't forget to gently, but firmly press ${conColor.green}ctrl-c${conColor.reset}${conColor.dim} when you need to exit the server!)${conColor.reset}`);
consolelog(` ${conColor.dim}And yes... that even works for you ${getOpSys()} ${conColor.dim}users!${conColor.reset}\n`);
consolelog(` Oh, and use ${conColor.yellow}express_server.js ${conColor.magenta}-quiet${conColor.reset} to run the server in silent mode!\n`);
});
//
// deal with any 'root/index' access to the locahost/domain
//
app.get("/", (req, res) => {
consolelog(`domain root level access requested -- forcing to login page`);
res.render("login.ejs");
});
//
// RENDER the main tiny URL index page (list all items)
//
app.get("/urls", (req, res) => {
cookieName(req);
let uidData = usersDatabase[uid];
if (!uidData) {
// need to login
res.render("login.ejs", {loginPage: "yes"});
} else {
urlsForUser(uidData); // assemble tiny URLS for this user
if (Object.keys(urlDatabase).length === 0) { // user has no TINY urls, jump them to CREATE page
const templateVars = { urls: urlDatabase, user: uidData, message: "Get started by creating your very first Tiny URL!"};
res.render("urls_new.ejs", templateVars);
} else {
const templateVars = { urls: urlDatabase, user: uidData, tracking: trackingDatabase};
res.render("urls_index.ejs", templateVars);
}
}
});
//
// RENDER for a NEW tiny URL entry page
//
app.get("/urls/new", (req, res) => { // NOTE: ORDER is important for nested /urls/ processing
if (cookieName(req) === "nobody") {
return res.status(403).render("login.ejs");
}
let uidData = usersDatabase[uid];
const templateVars = { urls: urlDatabase, user: uidData};
// IF no UID set then show the login page
if (uid === "nobody") {
res.render("login.ejs", {loginPage: "yes"});
} else {
res.render("urls_new.ejs", templateVars);
}
});
//
// DELETE an existing database entry
//
//app.post("/urls/:id/delete", (req, res) => {
app.delete("/urls/:id/delete", (req, res) => {
if (cookieName(req) === "nobody") {
return res.status(403).render("login.ejs");
}
if (hackCheck(req.params.id)) {
consolelog(`${req.params.id} - ${conColor.green}It's been ${conColor.red}nuked, ${conColor.orange}deleted, ${conColor.yellow}wiped out, ${conColor.cyan}obliterated & ${conColor.magenta}eliminated,${conColor.green} boss!\n`);
delete urlDatabaseMain[req.params.id];
}
return res.redirect('/urls/');
});
//
// UPDATE an existing database entry
//
// app.post("/urls/:id/update", (req, res) => {
app.put("/urls/:id/update", (req, res) => {
if (cookieName(req) === "nobody") {
return res.status(403).render("login.ejs");
}
consolelog("updating tiny url:" + req.params.id + " to " + req.body.longURL);
// TODO - need to error check req.body.longURL before changing the database (is a valid/working URL?)
if (urlExists(req.body.longURL) === false) {
return res.redirect('/urls/');
}
if (hackCheck(req.params.id)) {
urlDatabaseMain[req.params.id].longURL = req.body.longURL;
}
return res.redirect('/urls/');
});
//
// RENDER specific tiny URL page data (for review analytics or edit purposes)
//
app.get("/urls/:id", (req, res) => {
if (cookieName(req) === "nobody") {
return res.status(403).render("login.ejs");
}
if (hackCheck(req.params.id) === false) {
// let's leave - this isn't the owner of this url
return res.redirect('/urls/');
}
let uidData = usersDatabase[uid];
// grab click through total count from trackingDatabase
const totalCount = tinyTrack(trackingDatabase,req.params.id,"get");
// need to parse our clickDatabase and rebuild for this tinyURL id ONLY
let tempClicksDatabase = {};
let templateClicks = {};
let theusertype = '', graphArray = [], graphStats = {};
for (let item in clickDatabase) { // loop thru click database
if (clickDatabase[item].lid === req.params.id) { // filter matching tiny url items
if (clickDatabase[item].uid.length === 6) { // is the user registered or not
theusertype = '\u0020\u0020(Registered user)';
} else {
theusertype = '(Unregistered user)';
}
templateClicks = { // build temp database of clicks for this tiny url id
uid: clickDatabase[item].uid,
dateStamp: clickDatabase[item].dateStamp,
userType: theusertype,
};
tempClicksDatabase[makeID(8)] = templateClicks;
// ASSEMBLE GRAPH DATA:
let theHour = myDateObject.hours(clickDatabase[item].dateStamp);
if (!graphStats[theHour]) {
graphStats[theHour] = 1;
} else {
graphStats[theHour] += 1;
}
}
}
let moreStats = countUIDS(tempClicksDatabase); // count unique clicks
let clickUniques = Object.keys(moreStats).length;
// CONVERT GRAPH DATA for client side JS graph generator:
let newgraphObj = {}, fixeditem = '';
for (let item in graphStats) {
fixeditem = item.slice(-5);
newgraphObj[fixeditem] = graphStats[item];
graphArray.push(newgraphObj);
}
const templateVars = { id: req.params.id, longURL: urlDatabase[req.params.id], user: uidData, totalCount, logs:tempClicksDatabase, uniques:clickUniques, moreStats:moreStats, graphStats:graphStats};
res.render("urls_show.ejs", templateVars);
});
//
// CREATE a NEW tiny URL database Entry
//
app.post("/urls", (req, res) => {
if (cookieName(req) === "nobody") {
return res.status(403).render("login.ejs");
}
consolelog();
const newTinyURL = makeID();
if (req.body.longURL) {
// TODO - need to error check req.body.longURL before changing the database!
urlDatabase[newTinyURL] = req.body.longURL; // DEPRECATED
let dbRecord = {
longURL: req.body.longURL,
userID: uid,
};
urlDatabaseMain[newTinyURL] = dbRecord;
tinyTrack(trackingDatabase,newTinyURL,"addnew");
consolelog(`${conColor.magenta}New tiny URLs to play with! ${newTinyURL}${conColor.reset}`);
return res.redirect('/urls/' + newTinyURL);
} else {
consolelog(conColor.red + "User forgot data in new URL page. Resetting!" + conColor.reset);
return res.redirect('/urls/');
}
});
//
// REDIRECT to the longURL
//
app.get("/u/:id", (req, res) => {
cookieName(req);
let id = req.params.id;
if (id !== 'undefined' && (id in urlDatabaseMain)) { // does the id exist in main database?
const longURL = urlDatabaseMain[id].longURL;
consolelog(`${conColor.orange}Redirected to ${conColor.green}${longURL}${conColor.reset}`);
tinyTrack(trackingDatabase,id,'inc'); // increase total click count on this tiny URL
let tempuid;
if (uid === "" || uid === "nobody") {
tempuid = makeID(8); // create a temp user ID for even unregistered users
cookieName(req,"set",tempuid); // set a cookie for unregistered users too!
} else {
tempuid = uid;
}
clickTrack(clickDatabase, id, tempuid, 'add');
res.redirect(longURL);
return;
}
// assume bad tiny url and send to 404
consolelog(`${conColor.yellow}oops - ${conColor.red}undefined${conColor.yellow} isn't a valid destination${conColor.reset}\n`);
return res.status(404).render("url_notfound.ejs");
});
//
// REDIRECT to the REGISTER page
//
app.get("/register", (req, res) => {
if (isActualUser(cookieName(req))) { // uid is set if cookie set, but lets ensure uid is correct to our DB
consolelog("ACTIVE user found register page, we'll just jump to main page instead!");
return res.redirect('/urls/');
}
const templateVars = { urls: urlDatabase, loginPage: "yes"};
consolelog(`${conColor.green}New user visiting the login page.${conColor.reset}`);
res.render("newuser.ejs", templateVars);
});
//
// REDIRECT to the LOGIN page
//
app.get("/login", (req, res) => {
if (isActualUser(cookieName(req))) { // uid is set if cookie set, but lets ensure uid is correct to our DB
consolelog(`ACTIVE user found login page, we'll jump to main page instead!`);
return res.redirect('/urls/');
}
const templateVars = { urls: urlDatabase, loginPage: "yes"};
consolelog(`${conColor.green}This user needs to get ${conColor.cyan}signed in ${conColor.green} before they can do anything!${conColor.reset}`);
cookieName(req,"clear");
res.render("login.ejs", templateVars);
});
//
// LOGOUT by clearing COOKIE uid
//
app.get("/logout", (req, res) => {
consolelog();
cookieName(req,"clear");
consolelog(`${uid}${conColor.orange} Has logged out.${conColor.reset}`);
const templateVars = { loginPage: "yes"};
return res.render('login.ejs', templateVars);
});
//
// REGISTRATION handler
//
app.post("/register", (req,res) => {
let uid = makeID();
// set email, pass and uid into userDatabase 'structure'
const hashedPassword = bcrypt.hashSync(req.body.password, 10);
let userAccountObject = {
id: uid,
email: req.body.email,
password: hashedPassword,
};
// CHECK to ensure user entered an email address for account name - boot back to register if not and give message
if (!req.body.email) {
consolelog("\nUser forgot to enter their email address!");
const templateVars = { message: "You forgot to enter your email address!", loginPage: "yes"};
return res.render('newuser.ejs',templateVars);
}
// Does the account already exist?? search via emails!
let tempUID = findUserByEmail(req.body.email,usersDatabase);
if (tempUID) {
consolelog("User is already in the user database. Maybe forgot password?");
const templateVars = { message: "You're already registered as a user! Sign in instead!", loginPage: "yes"};
return res.render('login.ejs', templateVars);
}
// CHECK to ensure user supplied a password - boot them back to register page if not & give message
if (!req.body.password) {
consolelog("\nUser forgot to enter a password for their account!");
const templateVars = { message: "You forgot to enter a password for your account!", loginPage: "yes"};
return res.render('newuser.ejs',templateVars);
}
// add the user to the usersDatabase
usersDatabase[uid] = userAccountObject;
consolelog(usersDatabase[uid].email + ' - new user is joining the TinyApp family!'); // DEBUG
// consider the user logged in at this point - they've created an account successfully.
cookieName(req,"set",uid);
// redirect to urls page
return res.redirect('/urls/');
});
//
// LOGIN by setting COOKIE uid
//
app.post("/login", (req, res) => {
cookieName(req,"set","");
consolelog();
if (req.body.email) {
consolelog(`${conColor.magenta}${req.body.email}${conColor.orange} - welcome to TinyApp!${conColor.reset}`);
} else {
consolelog(`${conColor.yellow}User forgot to enter email on login.${conColor.reset}`);
}
// check to see if user exists
let tempUID = findUserByEmail(req.body.email,usersDatabase);
if (tempUID) { // users EXISTS and is password validated:
// check to see if password is valid
tempUID = validateUser(tempUID, req.body.password);
if (!tempUID) {
consolelog(tempUID + "watch for hack attempts - (wrong password)");
// jump to LOGIN page & show why failed to user (failed password)
const templateVars = { message: "Your password is wrong! Please check & try again!", loginPage: "yes"};
res.status(403).render("login.ejs", templateVars);
return;
}
// successfully logged in:
cookieName(req,"set",tempUID);
return res.redirect('/urls/');
} else {
consolelog(uid + ": We didn't find you in our user database!");
// jump to LOGIN page & show why failed to user (failed email address)
const templateVars = { message: "Your email address wasn't found in our user database!", loginPage: "yes"};
res.status(403).render("login.ejs", templateVars);
return;
}
});
//
// LOGOUT by clearing COOKIE uid
//
app.post("/logout", (req, res) => {
consolelog();
cookieName(res,"clear");
//cookieName(req,"set","");
consolelog(`${uid}${conColor.orange}is logged out.${conColor.reset}`);
res.render("login.ejs", { loginPage: "yes"});
});