├── .gitignore ├── .vscode └── settings.json ├── config ├── allowedOrigins.js ├── corsOptions.js ├── dbConn.js └── roles_list.js ├── controllers ├── authController.js ├── employeesController.js ├── logoutController.js ├── refreshTokenController.js └── registerController.js ├── logs ├── errLog.txt ├── errorLog.txt └── reqLog.txt ├── middleware ├── credentials.js ├── errorHandler.js ├── logEvents.js ├── logs │ └── reqLog.txt ├── verifyJWT.js └── verifyRoles.js ├── model ├── Employee.js ├── User.js ├── employees.json └── users.json ├── package-lock.json ├── package.json ├── public ├── css │ └── style.css ├── img │ └── img1.jpg └── text │ └── data │ ├── data.json │ └── data.txt ├── routes ├── api │ └── employees.js ├── auth.js ├── logout.js ├── refresh.js ├── register.js ├── root.js └── subdir.js ├── server-no-express.js ├── server.js └── views ├── 404.html ├── index.html ├── new-page.html └── subdir ├── index.html └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#15a50b", 4 | "activityBar.activeBorder": "#c3c7fb", 5 | "activityBar.background": "#15a50b", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#c3c7fb", 9 | "activityBarBadge.foreground": "#15202b", 10 | "sash.hoverBorder": "#15a50b", 11 | "statusBar.background": "#0f7508", 12 | "statusBar.foreground": "#e7e7e7", 13 | "statusBarItem.hoverBackground": "#15a50b", 14 | "statusBarItem.remoteBackground": "#0f7508", 15 | "statusBarItem.remoteForeground": "#e7e7e7", 16 | "titleBar.activeBackground": "#0f7508", 17 | "titleBar.activeForeground": "#e7e7e7", 18 | "titleBar.inactiveBackground": "#0f750899", 19 | "titleBar.inactiveForeground": "#e7e7e799" 20 | }, 21 | "peacock.color": "#0f7508" 22 | } -------------------------------------------------------------------------------- /config/allowedOrigins.js: -------------------------------------------------------------------------------- 1 | const allowedOrigins = [ 2 | "https://www.yoursite.com", 3 | "http://127.0.0.1:5500", // localhost public ip 4 | "http://localhost:3500", 5 | ]; 6 | 7 | module.exports = allowedOrigins; 8 | -------------------------------------------------------------------------------- /config/corsOptions.js: -------------------------------------------------------------------------------- 1 | // Cross Origin Resource Sharing 2 | const allowedOrigins = require("./allowedOrigins"); 3 | 4 | const corsOptions = { 5 | // origin parameter is whoever requested (ex: google.com) 6 | origin: (origin, callback) => { 7 | // if the domain is in the whitelist 8 | // '!origin' equiv to 'undefined', necessary b/c localhost doesn't serve an origin REMOVE AFTER DEVELOPMENT 9 | if (allowedOrigins.indexOf(origin) !== -1 || !origin) { 10 | // callback(error, origin_sent_back?) 11 | callback(null, true); 12 | } else { 13 | callback(new Error("Not allowed by CORS")); 14 | } 15 | }, 16 | optionsSuccessStatus: 200, 17 | }; 18 | 19 | module.exports = corsOptions; 20 | -------------------------------------------------------------------------------- /config/dbConn.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectDB = async () => { 4 | try { 5 | await mongoose.connect(process.env.DATABASE_URI, { 6 | useUnifiedTopology: true, 7 | useNewUrlParser: true, 8 | }); 9 | } catch (err) { 10 | console.log(err); 11 | } 12 | }; 13 | 14 | module.exports = connectDB; 15 | -------------------------------------------------------------------------------- /config/roles_list.js: -------------------------------------------------------------------------------- 1 | const ROLES_LIST = { 2 | Admin: 5150, 3 | Editor: 1984, 4 | User: 2001, 5 | }; 6 | 7 | module.exports = ROLES_LIST; 8 | -------------------------------------------------------------------------------- /controllers/authController.js: -------------------------------------------------------------------------------- 1 | const User = require("../model/User"); 2 | const bcrypt = require("bcrypt"); 3 | const jwt = require("jsonwebtoken"); 4 | 5 | const handleLogin = async (req, res) => { 6 | const { user, pwd } = req.body; 7 | if (!user || !pwd) 8 | return res 9 | .status(400) 10 | .json({ message: "Username & password are required." }); 11 | // find user 12 | const foundUser = await User.findOne({ username: user }).exec(); 13 | if (!foundUser) return res.sendStatus(401); // unauthorized 14 | // eval password 15 | const match = await bcrypt.compare(pwd, foundUser.password); 16 | if (match) { 17 | // get codes (not decrypted values) to send 18 | const roles = Object.values(foundUser.roles).filter(Boolean); 19 | // create JWT 20 | const accessToken = jwt.sign( 21 | { 22 | UserInfo: { 23 | username: foundUser.username, 24 | roles: roles, 25 | }, 26 | }, 27 | process.env.ACCESS_TOKEN_SECRET, 28 | { expiresIn: "30s" } // production would be 5 - 5 min 29 | ); 30 | // refreshToken doesn't need roles, just there for new accessToken 31 | const refreshToken = jwt.sign( 32 | { username: foundUser.username }, 33 | process.env.REFRESH_TOKEN_SECRET, 34 | { expiresIn: "1d" } 35 | ); 36 | // save refresh token to DB -- allows for token inval. on logout 37 | // will also be used to cross-reference to create a new accessToken 38 | foundUser.refreshToken = refreshToken; 39 | const result = await foundUser.save(); 40 | 41 | // send tokens to user 42 | // refresh token sent via httpOnly cookie 43 | res.cookie("jwt", refreshToken, { 44 | // maxAge & exp don't need to be identical when cookie deleted 45 | httpOnly: true, // httpOnly cookies are NOT available to JS 46 | maxAge: 24 * 60 * 60 * 1000, // 1 day 47 | sameSite: "None", 48 | // secure: true, 49 | }); 50 | // accessToken may be sent as simple json 51 | res.json({ accessToken }); 52 | } else { 53 | res.sendStatus(401); 54 | } 55 | }; 56 | 57 | module.exports = { handleLogin }; 58 | -------------------------------------------------------------------------------- /controllers/employeesController.js: -------------------------------------------------------------------------------- 1 | const Employee = require("../model/Employee"); 2 | 3 | const getAllEmployees = async (req, res) => { 4 | const employees = await Employee.find(); 5 | if (!employees) 6 | return res.status(204).json({ message: "No employees found" }); 7 | res.json(employees); 8 | }; 9 | 10 | const createNewEmployee = async (req, res) => { 11 | if (!req?.body?.firstname || !req?.body?.lastname) { 12 | return res 13 | .status(400) 14 | .json({ message: "First and last names are required" }); 15 | } 16 | 17 | try { 18 | const result = await Employee.create({ 19 | firstname: req.body.firstname, 20 | lastname: req.body.lastname, 21 | }); 22 | res.status(201).json(result); 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | }; 27 | 28 | const updateEmployee = async (req, res) => { 29 | if (!req.body?.id) { 30 | return res.status(400).json({ message: "ID parameter is required" }); 31 | } 32 | const employee = await Employee.findOne({ _id: req.body.id }).exec(); 33 | if (!employee) { 34 | return res 35 | .status(204) 36 | .json({ message: `No employee matches ID ${req.body.id}` }); 37 | } 38 | if (req.body?.firstname) employee.firstname = req.body.firstname; 39 | if (req.body?.lastname) employee.lastname = req.body.lastname; 40 | 41 | const result = await employee.save(); 42 | res.json(result); 43 | }; 44 | 45 | const deleteEmployee = async (req, res) => { 46 | if (!req?.body?.id) 47 | return res.status(400).json({ message: "Employee ID required." }); 48 | const employee = await Employee.findOne({ _id: req.body.id }).exec(); 49 | if (!employee) { 50 | return res 51 | .status(204) 52 | .json({ message: `No employee matches ID ${req.body.id}` }); 53 | } 54 | const result = await employee.deleteOne({ _id: req.body.id }); 55 | res.json(result); 56 | }; 57 | 58 | const getEmployee = async (req, res) => { 59 | if (!req?.params?.id) 60 | return res.status(400).json({ message: "Employee ID required." }); 61 | const employee = await Employee.findOne({ _id: req.params.id }).exec(); 62 | 63 | if (!employee) { 64 | return res 65 | .status(204) 66 | .json({ message: `No employee matches ID ${req.params.id}` }); 67 | } 68 | res.json(employee); 69 | }; 70 | 71 | module.exports = { 72 | getAllEmployees, 73 | createNewEmployee, 74 | updateEmployee, 75 | deleteEmployee, 76 | getEmployee, 77 | }; 78 | -------------------------------------------------------------------------------- /controllers/logoutController.js: -------------------------------------------------------------------------------- 1 | const User = require("../model/User"); 2 | 3 | const handleLogout = async (req, res) => { 4 | // on client also delete accessToken 5 | 6 | const cookies = req.cookies; 7 | if (!cookies?.jwt) return res.sendStatus(204); // no content 8 | const refreshToken = cookies.jwt; 9 | // find user (w/ refresh token) in DB 10 | const foundUser = await User.findOne({ refreshToken }).exec(); 11 | if (!foundUser) { 12 | res.clearCookie("jwt", { httpOnly: true, sameSite: "None", secure: true }); 13 | return res.sendStatus(204); // no content 14 | } 15 | // Delete Refresh Token in DB 16 | foundUser.refreshToken = ""; 17 | const result = await foundUser.save(); 18 | 19 | res.clearCookie("jwt", { 20 | httpOnly: true, 21 | sameSite: "None", 22 | secure: true, 23 | // maxAge not needed 24 | }); 25 | res.sendStatus(204); 26 | }; 27 | 28 | module.exports = { handleLogout }; 29 | -------------------------------------------------------------------------------- /controllers/refreshTokenController.js: -------------------------------------------------------------------------------- 1 | const User = require("../model/User"); 2 | const jwt = require("jsonwebtoken"); 3 | 4 | const handleRefreshToken = async (req, res) => { 5 | const cookies = req.cookies; 6 | if (!cookies?.jwt) return res.sendStatus(401); 7 | const refreshToken = cookies.jwt; 8 | // find user 9 | const foundUser = await User.findOne({ refreshToken }).exec(); 10 | if (!foundUser) return res.sendStatus(403); // Forbidden 11 | // eval JWT 12 | jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => { 13 | if (err || foundUser.username !== decoded.username) 14 | return res.sendStatus(403); // Forbidden 15 | const roles = Object.values(foundUser.roles); 16 | const accessToken = jwt.sign( 17 | { 18 | UserInfo: { 19 | username: decoded.username, 20 | roles: roles, 21 | }, 22 | }, 23 | process.env.ACCESS_TOKEN_SECRET, 24 | { expiresIn: "30s" } // prod: 5 - 15 min 25 | ); 26 | res.json({ accessToken }); 27 | }); 28 | }; 29 | 30 | module.exports = { handleRefreshToken }; 31 | -------------------------------------------------------------------------------- /controllers/registerController.js: -------------------------------------------------------------------------------- 1 | const User = require("../model/User"); 2 | const bcrypt = require("bcrypt"); 3 | 4 | const handleNewUser = async (req, res) => { 5 | const { user, pwd } = req.body; 6 | if (!user || !pwd) 7 | return res 8 | .status(400) 9 | .json({ message: "Username & password are required." }); 10 | // check for duplicate usernames in db 11 | const duplicate = await User.findOne({ username: user }).exec(); // exec goes after findOne with await 12 | if (duplicate) return res.sendStatus(409); // 409 = conflict 13 | try { 14 | // encrypt the password 15 | const hashedPwd = await bcrypt.hash(pwd, 10); 16 | // create & store the new user 17 | const result = await User.create({ 18 | username: user, 19 | password: hashedPwd, 20 | }); 21 | // other way 22 | // const newUser = new User() 23 | // newUser.username = user 24 | // const result = await newUser.save() 25 | // end other ways 26 | 27 | res.status(201).json({ success: `New user ${user} created!` }); 28 | } catch { 29 | res.status(500).json({ message: err.message }); 30 | } 31 | }; 32 | 33 | module.exports = { handleNewUser }; 34 | -------------------------------------------------------------------------------- /logs/errLog.txt: -------------------------------------------------------------------------------- 1 | 20220404 11:59:23 3c39bbff-3f7c-422a-8d39-f7eeacc8b49e Error: Not allowed by CORS 2 | 20220404 11:59:23 cdffc51e-0ddb-4220-be6b-12c9c43467d4 Error: Not allowed by CORS 3 | 20220404 11:59:24 de4b55df-bcad-466a-8496-e1af73b8995f Error: Not allowed by CORS 4 | 20220418 19:30:08 ca6498bb-8360-42cf-893a-6d5f86d9feca Error: ENOENT: no such file or directory, stat 'C:\Users\svenp\Coding Projects\Tutorials\nodejs\views\routes\index.html' 5 | 20220418 20:42:01 f437b747-043a-4f13-a17a-c78e5abcf4d1 Error: ENOENT: no such file or directory, stat 'C:\Users\svenp\Coding Projects\Tutorials\nodejs\views\subdir\new-page.html' 6 | 20220418 21:09:32 e0d00f1b-8813-457b-855d-73b81971e234 TypeError: req.json is not a function 7 | 20220419 21:41:43 3e8e1109-f785-4463-8554-993990a8d322 SyntaxError: Unexpected token } in JSON at position 28 8 | 20220424 11:44:40 3627da80-1a37-4e70-9403-af51f719a22d TypeError: Cannot read properties of undefined (reading 'find') 9 | 20220424 11:47:24 75296303-d30b-4ed9-89f4-71c4a753c750 TypeError: Assignment to constant variable. 10 | 20220424 11:48:11 caa9f77e-4831-43b5-8615-eaf1fbac414b TypeError: Cannot set properties of undefined (setting 'content-type') 11 | 20220424 11:49:58 4926e25a-dd73-43ab-bbc7-17ed65725fa2 TypeError: Cannot set properties of undefined (setting 'content-type') 12 | 20220424 12:08:11 7956d3fb-d95b-4d6f-8750-2f7a3a7030fd TypeError: Cannot set properties of undefined (setting 'content-type') 13 | 20220424 12:09:44 98f97198-9fe0-4852-bae4-21bba70f97d1 TypeError: Cannot set properties of undefined (setting 'content-type') 14 | 20220509 08:49:29 fb9bf0d3-88f9-49d8-b59f-75e80c55125e SyntaxError: Unexpected token c in JSON at position 13 15 | 20220509 08:50:41 8218b9e3-799a-42fa-a935-ce18f22223d5 SyntaxError: Unexpected token c in JSON at position 13 16 | -------------------------------------------------------------------------------- /logs/errorLog.txt: -------------------------------------------------------------------------------- 1 | 20220401 14:08:03 9c9dd084-d048-4af2-9c8f-e7271741c201 Error EISDIR: illegal operation on a directory, read 2 | 20220401 14:08:27 71a95cdd-616f-4b0c-b587-6808feced79e Error EISDIR: illegal operation on a directory, read 3 | -------------------------------------------------------------------------------- /logs/reqLog.txt: -------------------------------------------------------------------------------- 1 | 20220401 14:06:54 430b088f-6ac5-442c-b329-787432874443 /new-page.html GET 2 | 20220401 14:06:54 4b215048-a462-43c7-aed3-96d53320bfa8 /css/style.css GET 3 | 20220401 14:06:54 6e006a2f-11d5-4ace-aa7e-10e9574793aa /img/img1.jpg GET 4 | 20220401 14:06:54 52a860f8-74db-4fd0-b07f-31e3c616cfdf /favicon.ico GET 5 | 20220401 14:06:54 93cf5fa0-8f50-4c2c-bb49-528294157787 /favicon.ico GET 6 | 20220401 14:06:55 ff1e926e-12c3-4d82-ab63-faaf1eadd03e /favicon.ico GET 7 | 20220401 14:08:03 7cb371d2-ed57-4561-97a4-5fbdcc1eb0ea /subdir/ GET 8 | 20220401 14:08:12 92a6b5eb-a1cc-4032-9ad7-a61f4581185f /subdir GET 9 | 20220401 14:08:12 b45d6547-dc14-42dc-a9e2-96ea3e34b36f /css/style.css GET 10 | 20220401 14:08:12 76842aef-51d5-4613-960f-39c154f3095c /favicon.ico GET 11 | 20220401 14:08:13 69b26d6b-d2ba-49d2-918b-fedc9d5de670 /favicon.ico GET 12 | 20220401 14:08:27 490ed1a4-d04e-44b0-ae9d-3eaf21d2b07d /subdir/ GET 13 | 20220401 14:13:04 6e717024-9046-431b-b808-784a4d70fcb3 /subdir/ GET 14 | 20220401 14:13:04 5f6ccc14-7b1b-4088-9218-8460254da790 /favicon.ico GET 15 | 20220401 14:13:05 37ea8c89-1c19-438f-8718-fb0f3968f85b /favicon.ico GET 16 | 20220401 14:14:05 0510246b-2bb5-48ee-94f1-5b29b7c10ba3 / GET 17 | 20220401 14:14:05 1b4f300e-4845-4e4c-b19e-8ab1ff280a7e /css/style.css GET 18 | 20220401 14:14:05 7b6e45ef-c360-488f-bfa6-12e1e03f8c15 /favicon.ico GET 19 | 20220401 14:14:06 1582b8cb-4953-489c-8c29-d96560d8f28a /favicon.ico GET 20 | 20220404 11:29:11 5398320f-cf09-449d-96ee-a7a9c0f735f8 GET undefined / 21 | 20220404 11:29:11 60cde506-2b18-47fa-aa7f-0501fb7b5a61 GET undefined /css/style.css 22 | 20220404 11:29:11 d78a4e31-a4bb-4b6e-8bf9-b503d010a159 GET undefined /favicon.ico 23 | 20220404 11:29:12 4be1c44d-1ed9-49de-9dda-654d579dc6c3 GET undefined /favicon.ico 24 | 20220404 11:30:03 defed2f1-d6e4-4ff8-aca0-7dd90eb11809 GET undefined / 25 | 20220404 11:30:03 c1eca274-d293-402d-a88d-52e4ba0fbe0a GET undefined /css/style.css 26 | 20220404 11:30:03 770842fb-7fa6-4415-a32e-d620347c7960 GET undefined / 27 | 20220404 11:30:03 103b3a98-89b7-4486-9714-51e1a5bd88b2 GET undefined / 28 | 20220404 11:30:04 0c73c36e-80a4-484f-bde5-fe0213e5b703 GET undefined / 29 | 20220404 11:32:53 edb8a545-da51-4e28-bc1a-aa2c7ab14cd5 GET undefined / 30 | 20220404 11:32:53 a45fd822-8a21-42c5-a4df-9ffa2614356d GET undefined /css/style.css 31 | 20220404 11:32:53 72917775-b7d6-4b4f-bf46-63c3b18fdaa1 GET undefined / 32 | 20220404 11:32:53 a16b6676-1bac-4a79-9ba2-ae0639d7d120 GET undefined / 33 | 20220404 11:32:54 df7a3e41-e1ce-4c09-aec7-402b138dd5f0 GET undefined / 34 | 20220404 11:33:35 43ec2e5c-59fb-4659-baac-78eeb875737a GET https://www.google.com / 35 | 20220404 11:35:57 b44c64ba-4b92-4214-aaaa-a8b5ab9766c6 GET https://www.google.com / 36 | 20220404 11:45:04 782577af-e6ee-46ee-8a20-422e80bb33de GET https://www.google.com / 37 | 20220404 11:45:35 a1cf12d3-1589-498b-8cdc-ec7bbaa20cd2 GET https://www.google.com / 38 | 20220404 11:46:30 1ddf6952-4b44-43da-a3a2-0d319a35c947 GET https://www.google.com / 39 | 20220404 11:50:05 66117d2e-b09b-4384-b52e-532db583ec9a GET undefined / 40 | 20220404 11:50:05 9fd6af56-b7be-49cc-8f29-8d02455f5761 GET undefined /favicon.ico 41 | 20220404 11:50:06 8d2e8d43-385f-4d32-9c50-e3c5d9537efd GET undefined /favicon.ico 42 | 20220404 11:51:56 439436ad-9b8c-47a6-8cb8-ef7115520f6b GET undefined / 43 | 20220404 11:51:56 dd003797-21a0-4455-9496-c2abe26539a2 GET undefined /css/style.css 44 | 20220404 11:51:56 19de6c98-1477-482a-b70f-87f9cdfd4756 GET undefined / 45 | 20220404 11:51:57 0d9b1c86-0423-47d3-8076-9057fee2cb6e GET undefined / 46 | 20220404 11:51:57 444fa090-e8ef-480c-a574-0ad79f2e785d GET undefined / 47 | 20220404 11:59:23 6a23d3cb-8fb3-4717-ae2f-efc8cd55a32d GET undefined / 48 | 20220404 11:59:23 ccff1a06-05ab-4426-8570-2011e768c934 GET undefined /favicon.ico 49 | 20220404 11:59:24 76658b9b-62f0-4c7a-baee-fef581856f65 GET undefined /favicon.ico 50 | 20220404 12:00:12 aae04204-d875-4dcf-bc11-902f5ff7ac31 GET undefined / 51 | 20220404 12:00:12 d471d2ce-5349-4142-81bc-d7aea2e69d24 GET undefined /css/style.css 52 | 20220404 12:00:12 c176ace5-14b8-45c8-ada4-9c0b1c8bf3a5 GET undefined / 53 | 20220404 12:00:12 f59c2c96-e341-4b27-88eb-3da7a86bf725 GET undefined / 54 | 20220404 12:00:13 51dc2177-7bb9-4b6b-9c56-ba4475b831a4 GET undefined / 55 | 20220404 12:02:47 3b3abca6-86ca-4648-8f4a-a2a34cd552d7 GET undefined / 56 | 20220404 12:02:47 5f4aedd9-0430-4aa6-bde3-c1336b5d7360 GET undefined /css/style.css 57 | 20220404 12:02:47 e38a12fc-bbf9-45b8-8df0-63d83805c2fa GET undefined / 58 | 20220404 12:02:47 84f555a6-979f-4e74-b9a9-f1b687f197a3 GET undefined / 59 | 20220404 12:02:48 aa702463-8ab2-4fe7-acf7-48bd00623415 GET undefined / 60 | 20220404 12:02:55 7e3a5ae5-8523-429a-a910-3a3d20af76f6 GET undefined /dave 61 | 20220404 12:02:55 4b088e05-56be-4cd0-b16c-d841aa4e8255 GET undefined /css/style.css 62 | 20220404 12:02:55 daf0043c-8799-4601-a648-0d0195b6ad45 GET undefined /favicon.ico 63 | 20220404 12:02:56 1746a0f4-10a3-4760-b50e-c2cf00dd5a28 GET undefined /favicon.ico 64 | 20220404 12:05:27 41a62dd6-a117-44e7-b1c5-97b462c9b64f GET undefined /dave 65 | 20220404 12:05:27 5399f644-e492-451f-9934-cfab7b86b857 GET undefined /css/style.css 66 | 20220404 12:05:27 ebfc571b-13c7-47d1-8bac-6021b96c2dea GET undefined /favicon.ico 67 | 20220404 12:05:28 1886160b-450c-486d-8715-c0ed87c4367d GET undefined /favicon.ico 68 | 20220418 11:57:09 43cb1df0-11a9-40b3-b54a-ee7f00364ef2 GET undefined / 69 | 20220418 11:57:09 34f2a564-dc97-4303-976e-7b87ed75e5e5 GET undefined /css/style.css 70 | 20220418 11:57:10 af8d682f-b227-43e2-9cc8-cdadb7373824 GET undefined / 71 | 20220418 11:57:10 7d406939-9d5d-494e-8ceb-25041517638b GET undefined / 72 | 20220418 11:57:11 022af097-dfd9-4f11-b2bb-a3d7d86f5a34 GET undefined / 73 | 20220418 11:58:24 22ed2f9b-b690-44ff-96e5-8a9024b3aae4 GET undefined /subdir 74 | 20220418 11:58:24 79af2c5c-8783-4a81-b2a7-705264c012a6 GET undefined /favicon.ico 75 | 20220418 11:58:24 9ef43579-7d06-4529-973a-7fd997293bc8 GET undefined /favicon.ico 76 | 20220418 11:58:25 5d8d3e09-a7e8-405f-8601-b0d408e4e5c8 GET undefined /favicon.ico 77 | 20220418 11:58:36 63d12122-e03f-44f1-bfc5-f97d9abb9e4d GET undefined /test 78 | 20220418 11:58:36 0ca2a881-8243-4878-9afd-745deda491ca GET undefined /css/style.css 79 | 20220418 11:58:36 70c9031b-3e29-493e-b51d-adb42b5ced62 GET undefined /favicon.ico 80 | 20220418 11:58:37 2806c087-3e81-41d5-92e8-f3ec720a135a GET undefined /favicon.ico 81 | 20220418 11:58:42 9678753f-7d0c-4ada-be46-d91f2d07b057 GET undefined /test.html 82 | 20220418 11:58:42 8af489cb-385f-4212-a66e-a358bfcf1448 GET undefined /css/style.css 83 | 20220418 11:58:42 d261081e-2bd9-481d-a023-e62b413e74e0 GET undefined /favicon.ico 84 | 20220418 11:58:43 b404042b-4f15-413d-abcc-4d5342b02486 GET undefined /favicon.ico 85 | 20220418 11:58:53 5a6a1a4d-4373-4fc5-a688-e9a3e70fdcd1 GET undefined /subdir/test.html 86 | 20220418 11:58:53 6d9e4463-8cbf-4f45-9a6c-93cb2efae7a8 GET undefined /favicon.ico 87 | 20220418 11:58:54 b3e0cfa9-b5cf-41a0-9aea-049f311a82df GET undefined /favicon.ico 88 | 20220418 11:59:06 f6d2ee84-4f7a-47d3-84e8-927aad91cdcf GET undefined /subdir/test.html 89 | 20220418 11:59:06 38a714fc-94e2-48a5-82e1-90b25c94fe6d GET undefined /favicon.ico 90 | 20220418 11:59:08 2aa24953-da33-480c-acde-2b5b8d0015e7 GET undefined /favicon.ico 91 | 20220418 11:59:12 41a174af-0c9d-4b3a-ba7a-f848c8b88a81 GET undefined /subdir/dave.html 92 | 20220418 11:59:12 c64af6af-46ea-4863-9195-e089d4459f91 GET undefined /subdir/css/style.css 93 | 20220418 11:59:12 755e0773-c97c-4ab1-9be3-45864ad61471 GET undefined /favicon.ico 94 | 20220418 11:59:13 a49bc185-b702-45fd-bae9-4ffe90baa74c GET undefined /favicon.ico 95 | 20220418 12:00:11 0bdc63a1-0ff5-476d-a721-a22fd7beaa26 GET undefined /subdir/test.html 96 | 20220418 12:00:11 8c24cee1-e218-4d82-8668-82e1f047d6e3 GET undefined /favicon.ico 97 | 20220418 12:00:12 bf7b7529-f9b3-4489-986d-db11059d0321 GET undefined /favicon.ico 98 | 20220418 12:00:36 78c742ff-341a-4966-913d-54fb6843add9 GET undefined /subdir/d.html 99 | 20220418 12:00:36 fb688803-2488-4cf2-b1ba-dea2f1021fc8 GET undefined /subdir/css/style.css 100 | 20220418 12:00:37 8cbb16a0-732c-4fc9-bdd4-30f0e1689360 GET undefined /favicon.ico 101 | 20220418 12:00:38 7e7b3310-23cc-4a02-a308-f8431e3061be GET undefined /favicon.ico 102 | 20220418 19:30:08 9a42a794-3078-4d79-b5a2-a066f7082777 GET undefined / 103 | 20220418 19:30:08 894018bc-43fc-494b-b7b0-9ef9977f4f30 GET undefined /favicon.ico 104 | 20220418 19:30:10 2291c55f-a44b-4b1d-82db-2770d8a6b2e9 GET undefined /favicon.ico 105 | 20220418 20:41:33 36e77fc7-3d39-4ed3-97b8-428343ef0444 GET undefined / 106 | 20220418 20:41:33 b985b815-707b-490b-9928-52610e1bed56 GET undefined /favicon.ico 107 | 20220418 20:41:34 bef8f07a-7270-4514-816d-f099c8957fa4 GET undefined /favicon.ico 108 | 20220418 20:42:01 23b79875-183e-4bf2-998c-503849159992 GET undefined /new-page.html 109 | 20220418 20:42:01 4d81014d-88f0-4c77-bb8f-508b21c5a2ac GET undefined /favicon.ico 110 | 20220418 20:42:02 335577b3-981b-4d50-a462-87e3f001e29a GET undefined /favicon.ico 111 | 20220418 20:42:44 223e812e-2eaa-449f-915a-bd70d8e99285 GET undefined /new-page.html 112 | 20220418 20:42:44 4e7466c1-0499-4258-8d6d-d221141c2ce4 GET undefined /img/img1.jpg 113 | 20220418 20:42:44 64dd2423-d133-4693-8949-64538f280a7b GET undefined /css/style.css 114 | 20220418 20:42:44 bd0cd24d-6940-455f-9220-ca658b6e3731 GET undefined /favicon.ico 115 | 20220418 20:42:45 2e6265bb-4ead-4bba-9125-28bc5d4cf8fd GET undefined /favicon.ico 116 | 20220418 20:43:15 d18a2282-0847-42a6-9cca-99bf139d326c GET undefined /old-page.html 117 | 20220418 20:43:15 405054fe-4555-4c66-9fff-7c642da7e8f7 GET undefined /new-page.html 118 | 20220418 20:43:15 8aac4fec-6f73-4e51-b84f-97c939d0f4ae GET undefined /css/style.css 119 | 20220418 20:43:15 defb9882-782c-4173-8732-87947df82317 GET undefined /img/img1.jpg 120 | 20220418 20:43:15 153462a9-dd9e-4fed-ac98-c95b3cb781e2 GET undefined /favicon.ico 121 | 20220418 20:43:16 7a5e79f8-4738-485b-aa71-9c7af4ed6eb2 GET undefined /favicon.ico 122 | 20220418 20:43:26 bf3eefc9-e536-47bf-acdb-d8467c9172ea GET undefined /index 123 | 20220418 20:43:26 fb870bec-a6a8-462d-9723-2e03b55c2bba GET undefined /css/style.css 124 | 20220418 20:43:26 456b1d82-ede3-432e-9519-4ebde3c09f4f GET undefined /index 125 | 20220418 20:43:26 63a1dd43-b2eb-44b5-a6ec-3875ab9be10f GET undefined /index 126 | 20220418 20:43:27 d75ebb9b-b3b0-41f1-9432-a44006b228a2 GET undefined /index 127 | 20220418 21:07:17 9bbd8a20-2321-43d5-8527-5e2aa6ccf603 GET undefined /employees 128 | 20220418 21:09:32 41c5ca4a-6650-4b05-8120-710807bc42ce POST undefined /employees 129 | 20220418 21:11:38 0a90b08b-84e9-4553-9029-5f9e90f5ca7f POST undefined /employees 130 | 20220418 21:13:15 029c96cc-9566-4792-9bd9-94cfecf8b646 PUT undefined /employees 131 | 20220418 21:14:22 e52e0142-ccc0-4a45-b896-4aaa14ad7d15 DELETE undefined /employees 132 | 20220418 21:14:58 1bfc33ec-0534-47bf-9d84-84d7d19d3c2f DELETE undefined /employees 133 | 20220418 21:15:27 58597317-69a1-4bc3-bd16-44af4f9e6073 GET undefined /employees/1 134 | 20220419 17:19:17 ccd8a735-da61-4610-83ec-664d4c512a72 GET undefined /employees 135 | 20220419 17:20:12 32d17539-c8af-4b76-ab89-f5f992a0752b GET undefined /employees 136 | 20220419 17:21:25 99f26dbf-67de-4bff-8ea1-3278b241965f POST undefined /employees 137 | 20220419 17:22:06 82ca094b-e32a-442a-baef-4e8d9ad84967 POST undefined /employees 138 | 20220419 17:22:55 3afb08d8-5dda-4773-b1de-84ef36b9534e PUT undefined /employees 139 | 20220419 17:24:32 2d691abe-8be9-40dd-905a-8e6abd58e0d1 GET undefined /employees 140 | 20220419 17:26:14 5602f4f2-424b-4787-9852-d40a7c2b010d GET undefined /employees 141 | 20220419 17:26:24 d0e571ee-3fe6-496b-a236-ac19591f3634 POST undefined /employees 142 | 20220419 17:26:32 edb84abd-031d-418e-885e-c75bd9b5e458 PUT undefined /employees 143 | 20220419 17:28:00 aaa92be5-5cae-4c62-8e5f-e6dc64a0eff4 PUT undefined /employees 144 | 20220419 17:28:45 28b14edd-76be-4d59-934c-9459a7bc6276 GET undefined /employees 145 | 20220419 17:29:30 5949340e-d9b9-4579-8e2d-61686d434a5e GET undefined /employees 146 | 20220419 17:30:13 56b727da-3b4e-4f19-ae93-df4bae96dce6 GET undefined /employees 147 | 20220419 17:30:16 a205ce73-b96c-4fcb-a131-95323eb29a31 POST undefined /employees 148 | 20220419 17:30:21 3330deb9-f6b9-48e8-b95a-1652c20f2625 PUT undefined /employees 149 | 20220419 17:30:46 90831c65-9e06-481e-a2b5-4e94a5eeefe2 DELETE undefined /employees 150 | 20220419 17:31:13 79d1a054-6db6-497d-a699-68ae33d36e00 GET undefined /employees/1 151 | 20220419 17:31:13 33cac67b-8a59-48b8-9e6e-38f89fc234fc GET undefined /employees/css/style.css 152 | 20220419 17:32:41 16d60929-763b-44f1-835a-65d5a185118b GET undefined /employees/1 153 | 20220419 17:32:41 31232aa8-afbf-4572-96d7-c7aa3ab461f7 GET undefined /employees/css/style.css 154 | 20220419 17:33:50 4cd0cf6a-4603-44fe-a7d1-ea27227c497a GET undefined /employees/1 155 | 20220419 17:33:54 91b3a278-5cea-4b68-ac8a-5afb15fc270e GET undefined /employees/2 156 | 20220419 21:37:34 a815ab60-75d6-4e55-b061-0c429a938e38 POST undefined /register 157 | 20220419 21:38:15 edb159b8-8c40-4a5c-8223-3b11bbe83004 POST undefined /register 158 | 20220419 21:40:44 ab645136-3b33-4d18-98f4-521a2172557e POST undefined /register 159 | 20220419 21:41:43 e1d88f31-126e-4ce2-be0a-6f12e84ef018 POST undefined /register 160 | 20220419 21:41:49 78bbc145-1aaa-44b2-b80d-e54944ce8931 POST undefined /register 161 | 20220423 14:09:45 4e00ea69-149e-4ebe-9544-e781cf9f0c1c POST undefined /register 162 | 20220423 14:10:14 92fbe609-e3f1-49d2-946c-a7c435f46d60 POST undefined /register 163 | 20220423 14:10:41 070e1fa9-4987-426e-824d-8e73f322ad4b POST undefined /register 164 | 20220423 14:23:26 39d41cfa-273d-48c9-9d50-860fcf581017 POST undefined /auth 165 | 20220423 14:24:00 1d4cba6e-e5b7-4cf3-a29f-99804a19e000 POST undefined /auth 166 | 20220423 14:24:16 b9166ee6-cb34-4098-8bd7-f2ad5dc49266 POST undefined /register 167 | 20220423 15:19:27 bb335182-b295-4732-a406-294c7ff8a2d4 POST undefined /auth 168 | 20220423 15:19:34 5f177849-c8c3-49fe-aa1a-812e38bfc65a POST undefined /auth 169 | 20220423 15:21:32 0436d792-ea12-485c-b1d8-15f80997922b POST undefined /auth 170 | 20220423 15:21:58 b40d3332-73b6-4915-a751-e94cb4692f3a GET undefined /employees 171 | 20220423 15:22:16 da392718-57f7-4211-8ea2-465360a8093e GET undefined /employees 172 | 20220423 15:22:20 a5997f4d-1fa5-489a-b0e4-09a460caa8b9 POST undefined /auth 173 | 20220423 15:22:29 b1691201-2e94-41ac-9449-139262b851a8 GET undefined /employees 174 | 20220423 15:27:11 01e7c2b2-f318-4432-bcef-fc45525176e3 POST undefined /auth 175 | 20220423 15:27:21 5340ffa4-99e7-4b55-8905-08276c1a3137 POST undefined /employees 176 | 20220423 15:27:27 6ee151e7-d2bf-4ce4-a5bf-c2e2daa8b5ee GET undefined /employees 177 | 20220423 15:27:40 fdbbfb69-2c72-49d1-8487-2462aaa8043c DELETE undefined /employees 178 | 20220423 15:27:49 e66ed31b-6aa7-4566-9ee5-bb5290d0d58a GET undefined /employees/2 179 | 20220423 17:09:24 88d525d2-1ca6-4eda-a59f-0f1a88038beb POST undefined /auth 180 | 20220423 17:09:39 5a69ff89-7fae-499c-b1ba-3e4ac38198a1 GET undefined /auth 181 | 20220423 17:10:17 22084bff-7661-4205-a002-345e64ae64cd GET undefined /refresh 182 | 20220423 17:10:53 c7d0c18a-a618-4963-8c1e-008eab2e2c60 GET undefined /refresh 183 | 20220423 17:10:54 4b3d3106-5843-4a9f-a971-1c09a2fd611f GET undefined /refresh 184 | 20220423 17:10:55 30b21af8-35b6-4d9a-a237-9cb9226f76b3 GET undefined /refresh 185 | 20220423 17:10:56 a971b3b2-f2a4-4804-8383-bce2c3c1063b GET undefined /refresh 186 | 20220423 17:27:59 4a0731df-d587-42cd-8f1b-f83ffd6ae448 POST undefined /auth 187 | 20220423 17:29:02 b8f925fd-e93c-4297-9e66-4cb24df30835 GET undefined /logout 188 | 20220423 17:29:29 d0d397ac-ebee-4988-9f15-56fec648ce1c GET undefined /refresh 189 | 20220423 17:31:13 0478a2a5-99d1-4d53-82e3-49ecfe6ef0b9 POST undefined /auth 190 | 20220424 07:24:03 c9b13a0e-4f31-4974-a441-0b74726cffb0 POST undefined /auth 191 | 20220424 07:25:21 b713dbb4-a6ee-4b68-b289-6447f582b23d POST undefined /auth 192 | 20220424 11:36:04 eaa978a6-7080-40de-88e5-ed2c5d8b8cdd POST undefined /auth 193 | 20220424 11:36:31 9487d695-1a03-4a1b-8c55-be89cba1234e POST undefined /register 194 | 20220424 11:37:16 5db4b983-8160-48ee-86b1-0b3334e76ea5 POST undefined /auth 195 | 20220424 11:37:25 7fe197fe-7ff5-4acb-afb3-276dfc586319 POST undefined /auth 196 | 20220424 11:38:26 12087b49-e63d-4043-a54d-d7d170ceb122 POST undefined /auth 197 | 20220424 11:38:38 ccec1e4f-d203-47ae-b35d-4b85cd1c7a5e POST undefined /register 198 | 20220424 11:38:55 4a36befa-b407-45a6-818e-7960025209df POST undefined /register 199 | 20220424 11:42:31 ac28a4cc-cdc9-4c8b-9919-a148a8b168e1 POST undefined /auth 200 | 20220424 11:42:41 59264527-f4dd-45ad-80d1-f49672be5f65 POST undefined /auth 201 | 20220424 11:42:53 5b3e4b41-b50f-4018-beaf-80f3a82afb45 POST undefined /auth 202 | 20220424 11:43:02 b93a54b0-8bf3-4d3d-bb36-cd95faadfb56 POST undefined /auth 203 | 20220424 11:43:40 83aac82a-9c9e-4dff-b1cc-507dd6dbdb06 GET undefined /logout 204 | 20220424 11:44:11 e6d34440-34ae-477b-a51d-6c5ea9d5b635 POST undefined /auth 205 | 20220424 11:44:25 19c0fe7c-fb85-401b-89ba-1d82ebeba4b8 GET undefined /employees 206 | 20220424 11:44:40 202bd8cd-efef-43c6-9cc7-edb23796657c POST undefined /employees 207 | 20220424 11:47:07 b480f418-fc5d-4d20-84f6-9f480277020b POST undefined /auth 208 | 20220424 11:47:15 6c0e6d84-2017-49f7-80d1-fe1a14f903cf GET undefined /employees 209 | 20220424 11:47:24 d1557e32-8605-4cea-a852-94c6961c9f7c POST undefined /employees 210 | 20220424 11:47:48 b89d6edc-7cec-492b-adcc-8694427138a7 POST undefined /employees 211 | 20220424 11:48:02 3f0d26fc-37fe-4e48-afa2-4ff2934bfb3c POST undefined /auth 212 | 20220424 11:48:11 f49c4d22-4907-49cc-9970-57dfbffe9da4 POST undefined /employees 213 | 20220424 11:49:46 1ddf7279-f053-4b73-bd9f-537251271598 POST undefined /auth 214 | 20220424 11:49:53 faab6537-cf5a-494e-a043-ac8d18d323ab GET undefined /employees 215 | 20220424 11:49:58 6d0674ba-e822-4e76-94d6-5eba64749c8f POST undefined /employees 216 | 20220424 11:52:45 3265f202-2b27-44e6-b6f2-8357da8d1bf7 POST undefined /auth 217 | 20220424 11:52:56 afec8a8d-9d08-4c29-b05a-ca86ee9c541f POST undefined /auth 218 | 20220424 11:53:08 a94c1ba8-dc64-46d2-a581-c0ade8739a24 GET undefined /employees 219 | 20220424 11:53:42 ae610d6c-a93d-4030-906b-15788693675d GET undefined /employees 220 | 20220424 11:54:00 1d68b45d-e687-445e-a4dd-49401befc8d4 POST undefined /auth 221 | 20220424 11:54:15 eecd3dc9-530a-47a8-aa95-990deacab75e POST undefined /employees 222 | 20220424 11:55:21 f3a5ab2d-a75d-4049-96a5-fcafb329f106 POST undefined /auth 223 | 20220424 11:55:28 975131b7-3768-46ef-9bd5-fed2b3c64af5 POST undefined /employees 224 | 20220424 11:58:01 3e2e693e-a75c-4dad-a4a9-5a0472fe8221 POST undefined /auth 225 | 20220424 11:58:22 465438ae-5357-48c2-9051-982b8c372bd6 POST undefined /employees 226 | 20220424 12:01:02 49b9cbfd-3e1d-45ab-a52e-1aba7e38a729 POST undefined /auth 227 | 20220424 12:01:13 11aa95f2-582b-4a9a-9976-9587af3a6b02 POST undefined /employees 228 | 20220424 12:02:55 0bd42765-e23e-4717-869f-7752f10e930e POST undefined /auth 229 | 20220424 12:03:02 beb37de1-8146-42be-9a21-f2b2e306af47 POST undefined /employees 230 | 20220424 12:08:05 7897a758-70c1-42be-b918-b7ec8d318c54 POST undefined /auth 231 | 20220424 12:08:11 4c896ed5-9fac-4034-80b6-8f50d1fdffb5 DELETE undefined /employees 232 | 20220424 12:09:37 84f2af0d-c96d-47f0-9592-66b6fa1f3e58 POST undefined /auth 233 | 20220424 12:09:44 0fe7559f-5980-4ad3-8a1a-e7a78fc8a674 DELETE undefined /employees 234 | 20220424 12:12:45 45e48bc6-458e-469a-8f05-e1e86293e499 POST undefined /auth 235 | 20220424 12:12:54 ac20837f-0098-4acc-baec-022a979f0076 DELETE undefined /employees 236 | 20220424 12:14:22 a1c263cd-673a-49ca-901f-3463b599224e POST undefined /auth 237 | 20220424 12:14:30 a999ffa1-d1f6-4df1-9920-af0234835630 DELETE undefined /employees 238 | 20220424 12:14:39 69e8a308-d03d-4eff-972d-d4377820900d POST undefined /auth 239 | 20220424 12:14:46 f2b8eb5e-b68b-437e-a10c-d764e208f767 DELETE undefined /employees 240 | 20220424 12:17:48 ddbd2527-3345-4c2d-8b54-bfa44cf7a954 POST undefined /auth 241 | 20220424 12:17:50 94d09047-3b77-49e0-be40-8c23ab760736 GET undefined /refresh 242 | 20220428 11:50:59 04b2672b-c4ff-4a9a-983d-a33c423c3c26 POST undefined /register 243 | 20220428 11:51:47 8b7a59f8-e5f6-4d3d-a14d-955f60035afb POST undefined /register 244 | 20220502 07:57:41 2311cb48-d17e-4dd8-af0d-c5b9fdb1bd8c POST undefined /register 245 | 20220502 07:57:51 f7b1c111-27ee-41e6-b927-ca87bf6f1839 POST undefined /register 246 | 20220509 07:43:49 15ee66cd-6e53-401e-8663-f584c4bff10c POST undefined /auth 247 | 20220509 07:45:47 ab7c3560-5519-47e1-8a47-6984e128457b GET undefined /refresh 248 | 20220509 07:47:00 52f3bacd-583a-490b-ac9d-7a05ea9fd49c POST undefined /auth 249 | 20220509 07:47:03 9975508c-de4d-4d65-b3a6-ee18b0c7edf0 GET undefined /refresh 250 | 20220509 07:48:25 75a9c35d-6f12-4466-bbc1-af0ad24ef111 POST undefined /auth 251 | 20220509 07:48:29 06fadbd7-d86d-485e-96f4-78a471e87b58 GET undefined /logout 252 | 20220509 08:19:43 a98154cf-e2fb-4369-86a2-d2e918470b2c POST undefined /auth 253 | 20220509 08:20:23 fc2a5bef-8245-4395-8259-e31c7d70d427 POST undefined /auth 254 | 20220509 08:20:31 3e2b7772-e59e-41e8-b753-09ac846efbce GET undefined /employees 255 | 20220509 08:20:38 f74f2f02-0300-48fa-a74e-7d7000c3cebf GET undefined /employees 256 | 20220509 08:21:08 2a8e4549-94e3-4f41-9f9e-401c3b9b209c POST undefined /auth 257 | 20220509 08:21:16 feb5b000-5ef1-43d3-b418-93e8559acc0b GET undefined /employees 258 | 20220509 08:23:34 0c8c6883-2828-48fc-836a-30779a2f6c9d POST undefined /auth 259 | 20220509 08:23:43 515553c5-6bc1-4ff0-a98c-bc42e2ed850f GET undefined /employees/2 260 | 20220509 08:26:55 be09505b-4200-46c0-b6b2-d28db6d43bbe POST undefined /auth 261 | 20220509 08:27:06 aa776329-0385-4393-aa77-5238cca85638 GET undefined /employees 262 | 20220509 08:28:12 53d156b9-ad18-4a92-9cf2-a69d08fd7bc6 POST undefined /auth 263 | 20220509 08:28:21 242f6279-1693-4e18-8f2d-e201adb6ab74 GET undefined /employees 264 | 20220509 08:29:50 2a0ce98e-3e75-44c9-990c-173618cdbb04 POST undefined /auth 265 | 20220509 08:30:04 0bb4c26b-8a09-4772-854c-55dcd5bbc02c POST undefined /employees 266 | 20220509 08:31:41 f6307058-883d-4471-bf93-42ad818e17d1 POST undefined /auth 267 | 20220509 08:31:49 74b5f14d-9628-44d5-bddc-922941ccdc22 GET undefined /refresh 268 | 20220509 08:32:31 34c95951-071e-41bb-96b0-9b8457486949 POST undefined /employees 269 | 20220509 08:32:36 497741dd-ca31-4738-9c2b-5d014fcbaaf2 POST undefined /auth 270 | 20220509 08:32:39 8c5bdf2d-16b5-438c-b389-95fb8a5befd1 POST undefined /auth 271 | 20220509 08:32:47 59faaea7-9778-4d63-863b-b245e7d0bdbc POST undefined /employees 272 | 20220509 08:46:26 39884f6c-47ad-4b7e-a9bb-0e9f2da6bbaf POST undefined /auth 273 | 20220509 08:46:34 20f1f77f-baa9-42d8-a3f2-40afabbc368c GET undefined /employees 274 | 20220509 08:48:03 9658b52e-1fda-466d-9be8-e5e4ce48b722 POST undefined /auth 275 | 20220509 08:48:11 aaa41504-fce3-41a2-a280-af6d7f353657 GET undefined /employees/6279256c90400498a28b8bd0 276 | 20220509 08:49:11 3d93582a-56ae-4222-aca5-05b4fdae0bed POST undefined /auth 277 | 20220509 08:49:29 099fd765-91be-4234-8478-4a264263284f PUT undefined /employees 278 | 20220509 08:50:30 0b799bc1-2810-4c9d-8a02-6a3dce98c33e POST undefined /auth 279 | 20220509 08:50:41 3206a95e-c392-442e-9f51-e4333842300c PUT undefined /employees 280 | 20220509 08:56:40 526fbc24-b048-4ce6-9ef9-4e16c685f957 POST undefined /auth 281 | 20220509 08:56:51 77b99f61-6344-465c-a793-9ea8288ef4e4 PUT undefined /employees 282 | 20220509 08:57:03 087f79b3-7aaa-4200-80ee-93ea3debd562 GET undefined /employees 283 | 20220509 08:58:14 d4c21c77-5e4a-443f-b3ee-2456f3b71bec POST undefined /auth 284 | 20220509 08:58:23 ea6bd5b9-c49a-42da-8da8-630e356ca3ca DELETE undefined /employees 285 | -------------------------------------------------------------------------------- /middleware/credentials.js: -------------------------------------------------------------------------------- 1 | const allowedOrigins = require("../config/allowedOrigins"); 2 | 3 | const credentials = (req, res, next) => { 4 | const origin = req.headers.origin; 5 | if (allowedOrigins.includes(origin)) { 6 | res.header("Access-Control-Allow-Credentials", true); 7 | } 8 | next(); 9 | }; 10 | 11 | module.exports = credentials; 12 | -------------------------------------------------------------------------------- /middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | const { logEvents } = require("./logEvents"); 2 | 3 | const errorHandler = (err, req, res, next) => { 4 | logEvents(`${err.name}: ${err.message}`, "errLog.txt"); 5 | console.error(err.stack); 6 | res.status(500).send(err.message); 7 | }; 8 | 9 | module.exports = errorHandler; 10 | -------------------------------------------------------------------------------- /middleware/logEvents.js: -------------------------------------------------------------------------------- 1 | const { format } = require("date-fns"); 2 | const { v4: uuid } = require("uuid"); 3 | // common core 4 | const fs = require("fs"); 5 | const fsPromises = require("fs").promises; 6 | const path = require("path"); 7 | 8 | const logEvents = async (message, logName) => { 9 | const dateTime = `${format(new Date(), "yyyyMMdd\tHH:mm:ss")}`; 10 | const logItem = `${dateTime}\t${uuid()}\t${message}\n`; 11 | console.log(logItem); 12 | try { 13 | if (!fs.existsSync(path.join(__dirname, "..", "logs"))) { 14 | await fsPromises.mkdir(path.join(__dirname, "..", "logs")); 15 | } 16 | await fsPromises.appendFile( 17 | path.join(__dirname, "..", "logs", logName), 18 | logItem 19 | ); 20 | } catch (err) { 21 | console.error(err); 22 | } 23 | }; 24 | 25 | const logger = (req, res, next) => { 26 | logEvents(`${req.method}\t${req.headers.origin}\t${req.url}`, "reqLog.txt"); 27 | console.log(`${req.method} ${req.path}`); 28 | next(); 29 | }; 30 | 31 | module.exports = { logEvents, logger }; 32 | -------------------------------------------------------------------------------- /middleware/logs/reqLog.txt: -------------------------------------------------------------------------------- 1 | 20220404 11:28:43 6138810d-c50e-4b58-a26f-78702edc64a0 GET undefined / 2 | 20220404 11:28:43 fcdc5ef6-3847-4691-b403-6a652168d881 GET undefined /css/style.css 3 | 20220404 11:28:43 55694231-c225-4d64-9b4e-5fc3cfc6fe21 GET undefined /favicon.ico 4 | 20220404 11:28:44 3bb94c07-8721-4639-884f-22e3f38e3bdf GET undefined /favicon.ico 5 | -------------------------------------------------------------------------------- /middleware/verifyJWT.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const verifyJWT = (req, res, next) => { 4 | const authHeader = req.headers.authorization || req.headers.Authorization; 5 | if (!authHeader?.startsWith("Bearer ")) return res.sendStatus(401); 6 | const token = authHeader.split(" ")[1]; 7 | jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, decoded) => { 8 | if (err) return res.sendStatus(403); // invalid token (forbidden) 9 | req.user = decoded.UserInfo.username; 10 | req.roles = decoded.UserInfo.roles; 11 | next(); 12 | }); 13 | }; 14 | 15 | module.exports = verifyJWT; 16 | -------------------------------------------------------------------------------- /middleware/verifyRoles.js: -------------------------------------------------------------------------------- 1 | const verifyRoles = (...allowedRoles) => { 2 | return (req, res, next) => { 3 | if (!req?.roles) return res.sendStatus(401); 4 | const rolesArray = [...allowedRoles]; 5 | const result = req.roles 6 | .map((role) => rolesArray.includes(role)) 7 | .find((val) => val === true); 8 | if (!result) return res.sendStatus(401); // unauth 9 | next(); 10 | }; 11 | }; 12 | 13 | module.exports = verifyRoles; 14 | -------------------------------------------------------------------------------- /model/Employee.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const employeeSchema = new Schema({ 5 | firstname: { 6 | type: String, 7 | required: true, 8 | }, 9 | lastname: { 10 | type: String, 11 | required: true, 12 | }, 13 | }); 14 | 15 | module.exports = mongoose.model("Employee", employeeSchema); 16 | -------------------------------------------------------------------------------- /model/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema({ 5 | username: { 6 | type: String, 7 | required: true, 8 | }, 9 | roles: { 10 | User: { 11 | type: Number, 12 | default: 2001, 13 | }, 14 | Editor: Number, 15 | Admin: Number, 16 | }, 17 | password: { 18 | type: String, 19 | required: true, 20 | }, 21 | refreshToken: String, 22 | }); 23 | 24 | module.exports = mongoose.model("User", userSchema); 25 | -------------------------------------------------------------------------------- /model/employees.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "firstname": "Bobby", 5 | "lastname": "Joe" 6 | }, 7 | { 8 | "id": 2, 9 | "firstname": "Mary", 10 | "lastname": "Beth" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /model/users.json: -------------------------------------------------------------------------------- 1 | [{"username":"J2","password":"$2b$10$J0h/vC/ej317awGP5S769u8BB8QQu5PFquqPZVeWUU/kSuZzS0cBW","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkoyIiwiaWF0IjoxNjUwODIzOTY1LCJleHAiOjE2NTA5MTAzNjV9.3hwVPeEFay2fEM8jRhGPaKnBcKvHTAUymJFW45syH0w","roles":{"User":2001,"Editor":1984}},{"username":"J1","password":"$2b$10$1ghv1DQpLdOAEWprvj7ZNeg3TQ3HmbJG8QnLwsoQxiaaz.RCv.mi2","roles":{"User":2001,"Editor":1984,"Admin":5150},"refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkoxIiwiaWF0IjoxNjUwODI0MDc5LCJleHAiOjE2NTA5MTA0Nzl9.u2sR9_uPK7Spj2XXhFhWR5HUsAELdIGm-d73XJBUC34"},{"username":"J3","password":"$2b$10$0mHOJuAy67mS.zqk9mGUQOtBQvz97hWEdx4HO6UomO2lB.SqXm0RK","roles":{"User":2001},"refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkozIiwiaWF0IjoxNjUwODI0MjY4LCJleHAiOjE2NTA5MTA2Njh9.pLGbzKcJpB6XiFfYvawQTsCtxcu5hSxrR05WDlRnYpo"}] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "description": "Node js tutorial by Dave Gray", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server", 8 | "dev": "nodemon server", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Sven Moon", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcrypt": "^5.0.1", 15 | "cookie-parser": "^1.4.6", 16 | "cors": "^2.8.5", 17 | "date-fns": "^2.28.0", 18 | "dotenv": "^16.0.0", 19 | "express": "^4.17.3", 20 | "jsonwebtoken": "^8.5.1", 21 | "mongoose": "^6.3.1", 22 | "uuid": "^8.3.2" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^2.0.15" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-size: 36px; 9 | min-height: 100vh; 10 | color: #fff; 11 | background-color: #000; 12 | display: grid; 13 | place-content: center; 14 | } -------------------------------------------------------------------------------- /public/img/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sven-Moon/Tutorial-NodeJS-DaveGray/19abea4fe824b0485aac743ec9e24711cc28de00/public/img/img1.jpg -------------------------------------------------------------------------------- /public/text/data/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "firstname": "Sven", 4 | "lastname": "moon" 5 | }, 6 | { 7 | "firstname": "John", 8 | "lastname": "Smith" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /public/text/data/data.txt: -------------------------------------------------------------------------------- 1 | I'm some data.txt -------------------------------------------------------------------------------- /routes/api/employees.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const employeesController = require("../../controllers/employeesController"); 4 | const ROLES_LIST = require("../../config/roles_list"); 5 | const verifyRoles = require("../../middleware/verifyRoles"); 6 | 7 | router 8 | .route("/") 9 | .get(employeesController.getAllEmployees) 10 | .post( 11 | verifyRoles(ROLES_LIST.Admin, ROLES_LIST.Editor), 12 | employeesController.createNewEmployee 13 | ) 14 | .put( 15 | verifyRoles(ROLES_LIST.Admin, ROLES_LIST.Editor), 16 | employeesController.updateEmployee 17 | ) 18 | .delete(verifyRoles(ROLES_LIST.Admin), employeesController.deleteEmployee); 19 | 20 | // route with parameter(s) 21 | router.route("/:id").get(employeesController.getEmployee); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const authController = require("../controllers/authController"); 4 | 5 | router.post("/", authController.handleLogin); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /routes/logout.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const logoutController = require("../controllers/logoutController"); 4 | 5 | router.get("/", logoutController.handleLogout); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /routes/refresh.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const refreshTokenController = require("../controllers/refreshTokenController"); 4 | 5 | router.get("/", refreshTokenController.handleRefreshToken); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /routes/register.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const registerController = require("../controllers/registerController"); 4 | 5 | router.post("/", registerController.handleNewUser); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /routes/root.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const path = require("path"); 4 | 5 | router.get("^/$|/index(.html)?", (req, res) => { 6 | // method 1 (preferred) 7 | res.sendFile(path.join(__dirname, "..", "views", "index.html")); 8 | // method 2 9 | // res.sendFile("./views/index.html", { root: __dirname }); 10 | }); 11 | // new-page 12 | router.get("/new-page.html", (req, res) => { 13 | res.sendFile(path.join(__dirname, "..", "views", "new-page.html")); 14 | }); 15 | // old-page redirect 16 | router.get("/old-page(.html)?", (req, res) => { 17 | res.redirect(301, "/new-page.html"); // 302 by default 18 | }); 19 | 20 | module.exports = router; 21 | router; 22 | -------------------------------------------------------------------------------- /routes/subdir.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const path = require("path"); 4 | 5 | router.get("^/$|/index(.html)?", (req, res) => { 6 | res.sendFile(path.join(__dirname, "..", "views", "subdir", "index.html")); 7 | }); 8 | 9 | router.get("^/$|/test(.html)?", (req, res) => { 10 | res.sendFile(path.join(__dirname, "..", "views", "subdir", "test.html")); 11 | }); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /server-no-express.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const fsPromises = require("fs").promises; 5 | const logEvents = require("./logEvents"); 6 | const EventEmmitter = require("events"); 7 | class Emmitter extends EventEmmitter {} 8 | // initialize Object 9 | const myEmmitter = new Emmitter(); 10 | myEmmitter.on("log", (msg, fileName) => logEvents(msg, fileName)); 11 | 12 | // define webserver port 13 | const PORT = process.env.PORT || 3500; 14 | 15 | const serveFile = async (filePath, contentType, response) => { 16 | try { 17 | const rawData = await fsPromises.readFile( 18 | filePath, 19 | // if image type, don't use utf8 (pics won't show) 20 | !contentType.includes("image") ? "utf8" : "" 21 | ); 22 | const data = 23 | contentType === "application/json" ? JSON.parse(rawData) : rawData; 24 | response.writeHead(filePath.includes("404.html") ? 404 : 200, { 25 | "Content-Type": contentType, 26 | }); 27 | response.end( 28 | contentType === "application/json" ? JSON.stringify(data) : data 29 | ); 30 | } catch (err) { 31 | console.error(err); 32 | myEmmitter.emit("log", `${err.name}\t${err.message}`, "errorLog.txt"); 33 | response.statusCode = 500; 34 | response.end(); 35 | } 36 | }; 37 | 38 | const server = http.createServer((req, res) => { 39 | console.log(`${req.method}: ${req.url}`); 40 | 41 | myEmmitter.emit("log", `${req.url}\t${req.method}`, "reqLog.txt"); 42 | 43 | const extension = path.extname(req.url); 44 | 45 | let contentType; 46 | 47 | switch (extension) { 48 | case ".css": 49 | contentType = "text/css"; 50 | break; 51 | case ".js": 52 | contentType = "text/javascript"; 53 | break; 54 | case ".json": 55 | contentType = "application/json"; 56 | break; 57 | case ".jpg": 58 | contentType = "image/jpeg"; 59 | break; 60 | case ".png": 61 | contentType = "image/png"; 62 | break; 63 | case ".txt": 64 | contentType = "text/plain"; 65 | break; 66 | 67 | default: 68 | contentType = "text/html"; 69 | break; 70 | } 71 | 72 | let filePath = 73 | contentType === "text/html" && req.url === "/" 74 | ? path.join(__dirname, "views", "index.html") 75 | : // if last letter of url is '/' look at subdirectory 76 | (contentType === "text/html") & (req.url.slice(-1) === "/") 77 | ? path.join(__dirname, "views", req.url, "index.html") 78 | : contentType === "text/html" 79 | ? path.join(__dirname, "views", req.url) 80 | : path.join(__dirname, req.url); 81 | // makes .html extension not required in browser 82 | if (!extension && req.url.slice(-1) !== "/") filePath += ".html"; 83 | 84 | const fileExists = fs.existsSync(filePath); 85 | 86 | if (fileExists) { 87 | // serve the file 88 | serveFile(filePath, contentType, res); 89 | } else { 90 | // 404 91 | // 301 (redirect) 92 | switch (path.parse(filePath).base) { 93 | case "old-page.html": 94 | // this page has moved 95 | res.writeHead(301, { Location: "/new-page.html" }); 96 | res.end(); 97 | break; 98 | case "www.unknown-page.html": 99 | // redirect to root 100 | res.writeHead(301, { Location: "/" }); 101 | res.end(); 102 | break; 103 | default: 104 | // serve 404 105 | serveFile(path.join(__dirname, "views", "404.html"), "text/html", res); 106 | } 107 | } 108 | }); 109 | 110 | server.listen(PORT, () => console.log(`Server is running on port ${PORT}`)); 111 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const exp = require("constants"); 2 | const express = require("express"); 3 | const app = express(); 4 | const path = require("path"); 5 | const cors = require("cors"); 6 | const { logger } = require("./middleware/logEvents"); 7 | const errorHandler = require("./middleware/errorHandler"); 8 | const verifyJWT = require("./middleware/verifyJWT"); 9 | const cookieParser = require("cookie-parser"); 10 | require("dotenv").config(); 11 | const corsOptions = require("./config/corsOptions"); 12 | const credentials = require("./middleware/credentials"); 13 | const mongoose = require("mongoose"); 14 | const connectDB = require("./config/dbConn"); 15 | 16 | const PORT = process.env.PORT || 3500; 17 | 18 | // Connect to MongoDB 19 | connectDB(); 20 | 21 | // MIDDLEWARE 22 | // custom middleware logger 23 | app.use(logger); 24 | // Handle options credentials check - before CORS! 25 | // and fetch cookies credentials requiement 26 | app.use(credentials); 27 | // Cross Origin Resource Sharing 28 | app.use(cors(corsOptions)); 29 | 30 | // .use() applies middleware to all incoming routes 31 | // this handles form data 32 | app.use(express.urlencoded({ extended: false })); 33 | // handles NOT form json data 34 | app.use(express.json()); 35 | // middleware for cookies 36 | app.use(cookieParser()); 37 | 38 | // serve static files (such as css) 39 | app.use(express.static(path.join(__dirname, "./public"))); 40 | // adds the static files usage when in the subdir route 41 | app.use("/subdir", express.static(path.join(__dirname, "./public"))); 42 | 43 | // ROUTES 44 | app.use("/", require("./routes/root")); 45 | app.use("/register", require("./routes/register")); 46 | app.use("/auth", require("./routes/auth")); 47 | app.use("/refresh", require("./routes/refresh")); 48 | app.use("/logout", require("./routes/logout")); 49 | app.use("/subdir", require("./routes/subdir")); 50 | // All routes below will use verifyJWT 51 | app.use(verifyJWT); 52 | app.use("/employees", require("./routes/api/employees")); 53 | 54 | // app.use('/') 55 | app.all("*", (req, res) => { 56 | res.status(404); 57 | if (req.accepts("html")) { 58 | res.sendFile(path.join(__dirname, "views", "404.html")); 59 | } else if (req.accepts("json")) { 60 | res.json({ error: "404 Not found" }); 61 | } else { 62 | res.type("text").send("404 Not found"); 63 | } 64 | }); 65 | 66 | // default 404, note the status() chain 67 | // app.get("/*", (req, res) => { 68 | // res.status(404).sendFile(path.join(__dirname, "views", "404.html")); 69 | // }); 70 | 71 | // after everything else (except app.listen) 72 | app.use(errorHandler); 73 | 74 | mongoose.connection.once("open", () => { 75 | console.log("Connected to MongoDB"); 76 | app.listen(PORT, () => console.log(`Server is running on port ${PORT}`)); 77 | }); 78 | -------------------------------------------------------------------------------- /views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 8 | 9 | 10 | 11 | 12 |

404

13 | 14 | 15 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index 8 | 9 | 10 | 11 | 12 | 13 |

Index

14 | 15 | 16 | -------------------------------------------------------------------------------- /views/new-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | New Page 8 | 9 | 10 | 11 | 12 |

New Page

13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /views/subdir/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Subdirectory Index 8 | 9 | 10 | 11 |

Subdirectory Index

12 | 13 | 14 | -------------------------------------------------------------------------------- /views/subdir/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test Page 8 | 9 | 10 | 11 |

Test Page in /subdir/

12 | 13 | 14 | --------------------------------------------------------------------------------