From b1afdaa77672f38150f9a191fa87a36743e35c09 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 10:48:58 -0700 Subject: [PATCH 01/10] Added JWT to registration and login --- README.md | 39 +++++------- Server/controllers/authController.js | 27 +++++--- Server/db/FakeDb.js | 4 +- Server/package-lock.json | 94 ++++++++++++++++++++++++++++ Server/package.json | 1 + 5 files changed, 130 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 92e1b7bb1..f964a6c00 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,14 @@ BlueWave uptime monitoring application Configure the server with the following environmental variables -| ENV Varialbe Name | Required/Optional | Type | Description | Accepted Values | -| -------------------- | ----------------- | ------- | ---------------------------------------------------- | ------------------- | -| DB_TYPE | Optional | string | Specify DB to use | `MongoDB \| FakeDB` | -| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | | -| PORT | Optional | Integer | Specifies Port for Server | | -| MAILERSEND_API_KEY | Required | string | Specfies API KEY for MailerSend service | | -| SYSTEM_EMAIL_ADDRESS | Required | string | Specfies System email to be used in emailing service | | +| ENV Varialbe Name | Required/Optional | Type | Description | Accepted Values | +| -------------------- | ----------------- | --------- | ---------------------------------------------------- | ------------------- | +| JWT_SECRET | Required | `string` | JWT secret | | +| DB_TYPE | Optional | `string` | Specify DB to use | `MongoDB \| FakeDB` | +| DB_CONNECTION_STRING | Required | `string` | Specfies URL for MongoDB Database | | +| PORT | Optional | `integer` | Specifies Port for Server | | +| MAILERSEND_API_KEY | Required | `string` | Specfies API KEY for MailerSend service | | +| SYSTEM_EMAIL_ADDRESS | Required | `string` | Specfies System email to be used in emailing service | | --- @@ -130,9 +131,9 @@ Example: ##### Response Payload -> | Type | Notes | -> | ------ | ---------- | -> | `User` | User model | +> | Type | Notes | +> | ---- | ---------------------------------- | +> | JWT | JSON Web Token containing a `User` | ##### Sample CURL request @@ -191,9 +192,9 @@ curl --request POST \ ##### Response Payload -> | Type | Notes | -> | ------ | ---------- | -> | `User` | User model | +> | Type | Notes | +> | ---- | ---------------------------------- | +> | JWT | JSON Web Token Containing a `User` | ##### Sample CURL request @@ -213,17 +214,7 @@ curl --request POST \ { "success": true, "msg": "Found user", - "data": { - "_id": "6644fb9bae0b439371913969", - "firstname": "User First Name", - "lastname": "User Last Name", - "email": "user@gmail.com", - "isActive": true, - "isVerified": false, - "updated_at": "2024-05-15T18:14:51.049Z", - "created_at": "2024-05-15T18:14:51.049Z", - "__v": 0 - } + "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NjQ1MDc5YWFlMGI0MzkzNzE5MTM5NzIiLCJmaXJzdG5hbWUiOiJBbGV4IiwibGFzdG5hbWUiOiJIb2xsaWRheSIsImVtYWlsIjoiYWpob2xsaWRAZ21haWwuY29tIiwiaXNBY3RpdmUiOnRydWUsImlzVmVyaWZpZWQiOmZhbHNlLCJ1cGRhdGVkX2F0IjoiMjAyNC0wNS0xNVQxOTowNjowMi43MjBaIiwiY3JlYXRlZF9hdCI6IjIwMjQtMDUtMTVUMTk6MDY6MDIuNzIwWiIsIl9fdiI6MCwiaWF0IjoxNzE2Mzk5MDM2fQ.clFR01gh5XK9DtLjB-_JLxMBAzBZuduRQv3ZzqHuTRY" } ``` diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index e8fdc739e..2c0c8d755 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,7 +1,9 @@ const express = require("express"); -const UserModel = require("../models/user"); const { registerValidation, loginValidation } = require("../validation/joi"); const logger = require("../utils/logger"); +require("dotenv").config(); + +var jwt = require("jsonwebtoken"); /** * @function @@ -42,19 +44,25 @@ const registerController = async (req, res) => { // TODO: Send an email to user // Will add this later logger.info("New user created!", { service: "auth", userId: newUser._id }); + const token = jwt.sign(newUser, process.env.JWT_SECRET); + return res .status(200) - .json({ success: true, msg: "User created}", data: newUser }); + .json({ success: true, msg: "User created}", data: token }); } catch (error) { logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); } }; -// ************************** -// Handles logging in a user -// Returns a user at the moment, but will likely return a JWT in the future -// ************************** +/** + * Returns JWT with User info + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ const loginController = async (req, res) => { try { // Validate input @@ -69,7 +77,6 @@ const loginController = async (req, res) => { } // Compare password const match = await user.comparePassword(req.body.password); - console.log(user); if (!match) { throw new Error("Password does not match!"); } @@ -80,11 +87,11 @@ const loginController = async (req, res) => { const userWithoutPassword = { ...user._doc }; delete userWithoutPassword.password; - // Happy path, return user - // In the future we'll probably return a JWT instead of a user + // Happy path, return token + const token = jwt.sign(userWithoutPassword, process.env.JWT_SECRET); return res .status(200) - .json({ success: true, msg: "Found user", data: userWithoutPassword }); + .json({ success: true, msg: "Found user", data: token }); } catch (error) { // Anything else should be an error logger.error(error.message, { service: "auth" }); diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index b2abd919e..6a65bf502 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -41,7 +41,9 @@ const insertUser = async (req, res) => { const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync USERS.push(newUser); - return newUser; + const userToReturn = { ...newUser._doc }; + delete userToReturn.password; + return userToReturn; } catch (error) { throw error; } diff --git a/Server/package-lock.json b/Server/package-lock.json index 495d5bc2e..9ee830ebd 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -15,6 +15,7 @@ "express": "^4.19.2", "helmet": "^7.1.0", "joi": "^17.13.1", + "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", "mongoose": "^8.3.3", "winston": "^3.13.0" @@ -310,6 +311,11 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -535,6 +541,14 @@ "url": "https://dotenvx.com" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1073,6 +1087,51 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -1086,6 +1145,41 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/logform": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", diff --git a/Server/package.json b/Server/package.json index 6a082cbdc..60777f7a5 100644 --- a/Server/package.json +++ b/Server/package.json @@ -17,6 +17,7 @@ "express": "^4.19.2", "helmet": "^7.1.0", "joi": "^17.13.1", + "jsonwebtoken": "9.0.2", "mailersend": "^2.2.0", "mongoose": "^8.3.3", "winston": "^3.13.0" From 7dbd3b28d2c35014f433dad4a055b65b423fcc1a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 10:53:58 -0700 Subject: [PATCH 02/10] Refactored issuing token to function --- Server/middleware/jwtVerification.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 Server/middleware/jwtVerification.js diff --git a/Server/middleware/jwtVerification.js b/Server/middleware/jwtVerification.js new file mode 100644 index 000000000..0472b98ee --- /dev/null +++ b/Server/middleware/jwtVerification.js @@ -0,0 +1 @@ +const verifyJwt = (req, res, next) => {}; From 073f8b2e30e64a7a3daaf73f56b4f25e8a1eb305 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 11:38:36 -0700 Subject: [PATCH 03/10] Added verification middleware, protected monitor routes --- Server/controllers/authController.js | 17 +++++++--- Server/controllers/monitorController.js | 1 - Server/index.js | 5 +-- Server/middleware/jwtVerification.js | 1 - Server/middleware/verifyJWT.js | 42 +++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 8 deletions(-) delete mode 100644 Server/middleware/jwtVerification.js create mode 100644 Server/middleware/verifyJWT.js diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 2c0c8d755..07306d1cf 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -2,9 +2,18 @@ const express = require("express"); const { registerValidation, loginValidation } = require("../validation/joi"); const logger = require("../utils/logger"); require("dotenv").config(); - var jwt = require("jsonwebtoken"); +/** + * Creates and returns JWT token with an arbitrary payload + * @function + * @param {Object} payload + * @returns {String} + */ +const issueToken = (payload) => { + return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "1h" }); +}; + /** * @function * @param {express.Request} req @@ -44,11 +53,11 @@ const registerController = async (req, res) => { // TODO: Send an email to user // Will add this later logger.info("New user created!", { service: "auth", userId: newUser._id }); - const token = jwt.sign(newUser, process.env.JWT_SECRET); + const token = issueToken(newUser); return res .status(200) - .json({ success: true, msg: "User created}", data: token }); + .json({ success: true, msg: "User created", data: token }); } catch (error) { logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); @@ -88,7 +97,7 @@ const loginController = async (req, res) => { delete userWithoutPassword.password; // Happy path, return token - const token = jwt.sign(userWithoutPassword, process.env.JWT_SECRET); + const token = issueToken(userWithoutPassword); return res .status(200) .json({ success: true, msg: "Found user", data: token }); diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 8b2f72ab1..3a46427c4 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -5,7 +5,6 @@ const { } = require("../validation/joi"); const logger = require("../utils/logger"); -const Monitor = require("../models/Monitor"); /** * Returns all monitors diff --git a/Server/index.js b/Server/index.js index 4d2765cd8..5068c98d8 100644 --- a/Server/index.js +++ b/Server/index.js @@ -6,6 +6,8 @@ const monitorRouter = require("./routes/monitorRoute"); const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); const logger = require("./utils/logger"); +var { verifyJWT } = require("./middleware/verifyJWT"); + // const { sendEmail } = require('./utils/sendEmail') // ************************** @@ -58,8 +60,7 @@ app.use((req, res, next) => { //routes app.use("/api/v1/auth", authRouter); - -app.use("/api/v1/monitors", monitorRouter); +app.use("/api/v1/monitors", verifyJWT, monitorRouter); // Testing email service // app.use('/sendEmail', async (req, res) => { diff --git a/Server/middleware/jwtVerification.js b/Server/middleware/jwtVerification.js deleted file mode 100644 index 0472b98ee..000000000 --- a/Server/middleware/jwtVerification.js +++ /dev/null @@ -1 +0,0 @@ -const verifyJwt = (req, res, next) => {}; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js new file mode 100644 index 000000000..53464c606 --- /dev/null +++ b/Server/middleware/verifyJWT.js @@ -0,0 +1,42 @@ +const jwt = require("jsonwebtoken"); +const logger = require("../utils/logger"); +const SERVICE_NAME = "verifyJWT"; +const TOKEN_PREFIX = "Bearer "; + +/** + * Verifies the JWT token + * @function + * @param {express.Request} req + * @param {express.Response} res + * @param {express.NextFunction} next + * @returns {express.Response} + */ +const verifyJWT = (req, res, next) => { + const token = req.headers["authorization"]; + // Make sure a token is provided + if (!token) { + logger.error("No token provided", { service: SERVICE_NAME }); + return res.status(401).json({ success: false, msg: "No token provided" }); + } + // Make sure it is properly formatted + if (token.startsWith(TOKEN_PREFIX)) { + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // Verify the token's authenticity + jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => { + if (err) { + logger.error("Invalid token", { service: SERVICE_NAME }); + return res.status(401).json({ success: false, msg: "Invalid token" }); + } + //Add the user to the request object for use in the route + req.user = decoded; + next(); + }); + } else { + logger.error("Invalid token format", { service: SERVICE_NAME }); + return res + .status(400) + .json({ success: false, msg: "Invalid token format" }); + } +}; + +module.exports = { verifyJWT }; From ddd7152683b3aff3de0d3baed73d85c10875cff6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 11:42:29 -0700 Subject: [PATCH 04/10] Removed old comments --- Server/controllers/authController.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 07306d1cf..2d4fe310d 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -90,9 +90,7 @@ const loginController = async (req, res) => { throw new Error("Password does not match!"); } - // Copy user to remove password. More memory, but better than mutating user? - // We can move this to the DB layer if we want - // Probably not neccessary at all as we'll likely return a JWT instead of a user + // Remove password from user object. Should this be abstracted to DB layer? const userWithoutPassword = { ...user._doc }; delete userWithoutPassword.password; From a7de58c99b7c99facbbbe685ee679f523c553b61 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 11:49:14 -0700 Subject: [PATCH 05/10] Refactored service name, resolves #41 --- Server/controllers/authController.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 2d4fe310d..90a12e244 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -3,6 +3,7 @@ const { registerValidation, loginValidation } = require("../validation/joi"); const logger = require("../utils/logger"); require("dotenv").config(); var jwt = require("jsonwebtoken"); +const SERVICE_NAME = "auth"; /** * Creates and returns JWT token with an arbitrary payload @@ -34,16 +35,16 @@ const registerController = async (req, res) => { try { const isUser = await req.db.getUserByEmail(req, res); if (isUser) { - logger.error("User already exists!", { - service: "auth", + logger.error("User already exists", { + service: SERVICE_NAME, userId: isUser._id, }); return res .status(400) - .json({ success: false, msg: "User already exists!" }); + .json({ success: false, msg: "User already exists" }); } } catch (error) { - logger.error(error.message, { service: "auth" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } @@ -52,14 +53,17 @@ const registerController = async (req, res) => { const newUser = await req.db.insertUser(req, res); // TODO: Send an email to user // Will add this later - logger.info("New user created!", { service: "auth", userId: newUser._id }); + logger.info("New user created!", { + service: SERVICE_NAME, + userId: newUser._id, + }); const token = issueToken(newUser); return res .status(200) .json({ success: true, msg: "User created", data: token }); } catch (error) { - logger.error(error.message, { service: "auth" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -101,7 +105,7 @@ const loginController = async (req, res) => { .json({ success: true, msg: "Found user", data: token }); } catch (error) { // Anything else should be an error - logger.error(error.message, { service: "auth" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; From 0b333384ac7577b203147185b7846e1b3b3f783a Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Wed, 22 May 2024 16:46:09 -0400 Subject: [PATCH 06/10] Updated Contributors in readme file - Added team member --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f964a6c00..82af5013d 100644 --- a/README.md +++ b/README.md @@ -282,3 +282,5 @@ curl --request POST \ + +Made with [contrib.rocks](https://contrib.rocks). From 3537f3fa194169f6e6f08b28dc3f9d7bbf87a2ef Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Wed, 22 May 2024 17:14:34 -0400 Subject: [PATCH 07/10] Added subroutes to homelayout and created basic respective components, and removed demo components from home page #45 --- Client/src/App.jsx | 13 ++++++++++++- Client/src/Pages/Home/index.jsx | 4 ---- Client/src/Pages/Incidents/index.css | 0 Client/src/Pages/Incidents/index.jsx | 9 +++++++++ Client/src/Pages/Integrations/index.css | 0 Client/src/Pages/Integrations/index.jsx | 9 +++++++++ Client/src/Pages/Monitors/index.css | 0 Client/src/Pages/Monitors/index.jsx | 9 +++++++++ Client/src/Pages/Settings/index.css | 0 Client/src/Pages/Settings/index.jsx | 9 +++++++++ Client/src/Pages/Status/index.css | 0 Client/src/Pages/Status/index.jsx | 9 +++++++++ 12 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 Client/src/Pages/Incidents/index.css create mode 100644 Client/src/Pages/Incidents/index.jsx create mode 100644 Client/src/Pages/Integrations/index.css create mode 100644 Client/src/Pages/Integrations/index.jsx create mode 100644 Client/src/Pages/Monitors/index.css create mode 100644 Client/src/Pages/Monitors/index.jsx create mode 100644 Client/src/Pages/Settings/index.css create mode 100644 Client/src/Pages/Settings/index.jsx create mode 100644 Client/src/Pages/Status/index.css create mode 100644 Client/src/Pages/Status/index.jsx diff --git a/Client/src/App.jsx b/Client/src/App.jsx index cfbc69f50..9b21de1a4 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -6,12 +6,23 @@ import Register from "./Pages/Register"; import HomeLayout from "./Layouts/HomeLayout"; import Demo from "./Pages/Demo/Demo"; import PlayGround from "./Pages/PlayGround/PlayGround"; +import Monitors from "./Pages/Monitors" +import Incidents from "./Pages/Incidents"; +import Status from "./Pages/Status"; +import Integrations from "./Pages/Integrations"; +import Settings from "./Pages/Settings"; function App() { return ( <> - } /> + } > + } /> + } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/Client/src/Pages/Home/index.jsx b/Client/src/Pages/Home/index.jsx index a1ad8e314..fa932dad9 100644 --- a/Client/src/Pages/Home/index.jsx +++ b/Client/src/Pages/Home/index.jsx @@ -8,10 +8,6 @@ const Home = () => { return ( <>
Home
- - - - ); }; diff --git a/Client/src/Pages/Incidents/index.css b/Client/src/Pages/Incidents/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx new file mode 100644 index 000000000..e1aa44740 --- /dev/null +++ b/Client/src/Pages/Incidents/index.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Incidents = () => { + return ( +
Incidents
+ ) +} + +export default Incidents \ No newline at end of file diff --git a/Client/src/Pages/Integrations/index.css b/Client/src/Pages/Integrations/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Integrations/index.jsx b/Client/src/Pages/Integrations/index.jsx new file mode 100644 index 000000000..1df87ee52 --- /dev/null +++ b/Client/src/Pages/Integrations/index.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Integrations = () => { + return ( +
Integrations
+ ) +} + +export default Integrations \ No newline at end of file diff --git a/Client/src/Pages/Monitors/index.css b/Client/src/Pages/Monitors/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx new file mode 100644 index 000000000..6d4e3d59d --- /dev/null +++ b/Client/src/Pages/Monitors/index.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Monitors = () => { + return ( +
Monitors
+ ) +} + +export default Monitors \ No newline at end of file diff --git a/Client/src/Pages/Settings/index.css b/Client/src/Pages/Settings/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx new file mode 100644 index 000000000..639bbef54 --- /dev/null +++ b/Client/src/Pages/Settings/index.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Settings = () => { + return ( +
Settings
+ ) +} + +export default Settings \ No newline at end of file diff --git a/Client/src/Pages/Status/index.css b/Client/src/Pages/Status/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Status/index.jsx b/Client/src/Pages/Status/index.jsx new file mode 100644 index 000000000..33729f7e1 --- /dev/null +++ b/Client/src/Pages/Status/index.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Status = () => { + return ( +
Status
+ ) +} + +export default Status \ No newline at end of file From f4b0034281a664c679a62fdf07f562e7bf476c5c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 14:38:47 -0700 Subject: [PATCH 08/10] Added new endpoints to readme, added cURL and Sample respones to existing endpoint docs, fixed minor bugs discovered during documentation --- .gitignore | 3 +- README.md | 291 ++++++++++++++++++++++-- Server/controllers/authController.js | 2 +- Server/controllers/monitorController.js | 5 +- 4 files changed, 273 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 9f1f131d6..df54c0e01 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.vite \ No newline at end of file +.vite +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 82af5013d..9d362d5c3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ BlueWave uptime monitoring application #### Installation 1. Change directory to the `Server` directory -2. Install all depencies by running `npm install` +2. Install all dependencies by running `npm install` --- @@ -39,14 +39,14 @@ BlueWave uptime monitoring application Configure the server with the following environmental variables -| ENV Varialbe Name | Required/Optional | Type | Description | Accepted Values | -| -------------------- | ----------------- | --------- | ---------------------------------------------------- | ------------------- | -| JWT_SECRET | Required | `string` | JWT secret | | -| DB_TYPE | Optional | `string` | Specify DB to use | `MongoDB \| FakeDB` | -| DB_CONNECTION_STRING | Required | `string` | Specfies URL for MongoDB Database | | -| PORT | Optional | `integer` | Specifies Port for Server | | -| MAILERSEND_API_KEY | Required | `string` | Specfies API KEY for MailerSend service | | -| SYSTEM_EMAIL_ADDRESS | Required | `string` | Specfies System email to be used in emailing service | | +| ENV Variable Name | Required/Optional | Type | Description | Accepted Values | +| -------------------- | ----------------- | --------- | ----------------------------------------------------- | ------------------- | +| JWT_SECRET | Required | `string` | JWT secret | | +| DB_TYPE | Optional | `string` | Specify DB to use | `MongoDB \| FakeDB` | +| DB_CONNECTION_STRING | Required | `string` | Specifies URL for MongoDB Database | | +| PORT | Optional | `integer` | Specifies Port for Server | | +| MAILERSEND_API_KEY | Required | `string` | Specifies API KEY for MailerSend service | | +| SYSTEM_EMAIL_ADDRESS | Required | `string` | Specifies System email to be used in emailing service | | --- @@ -154,18 +154,8 @@ curl --request POST \ ```json { "success": true, - "msg": "User created}", - "data": { - "_id": "6645079aae0b439371913972", - "firstname": "User First Name", - "lastname": "User Last Name", - "email": "user@gmail.com", - "isActive": true, - "isVerified": false, - "updated_at": "2024-05-15T19:06:02.720Z", - "created_at": "2024-05-15T19:06:02.720Z", - "__v": 0 - } + "msg": "User created", + "data": "" } ``` @@ -214,7 +204,7 @@ curl --request POST \ { "success": true, "msg": "Found user", - "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NjQ1MDc5YWFlMGI0MzkzNzE5MTM5NzIiLCJmaXJzdG5hbWUiOiJBbGV4IiwibGFzdG5hbWUiOiJIb2xsaWRheSIsImVtYWlsIjoiYWpob2xsaWRAZ21haWwuY29tIiwiaXNBY3RpdmUiOnRydWUsImlzVmVyaWZpZWQiOmZhbHNlLCJ1cGRhdGVkX2F0IjoiMjAyNC0wNS0xNVQxOTowNjowMi43MjBaIiwiY3JlYXRlZF9hdCI6IjIwMjQtMDUtMTVUMTk6MDY6MDIuNzIwWiIsIl9fdiI6MCwiaWF0IjoxNzE2Mzk5MDM2fQ.clFR01gh5XK9DtLjB-_JLxMBAzBZuduRQv3ZzqHuTRY" + "data": "" } ``` @@ -237,6 +227,49 @@ curl --request POST \ > | ---------------- | ----------------- | > | `Array[Monitor]` | Array of monitors | +###### Sample cURL Request + +``` +curl --request GET \ + --url http://localhost:5000/api/v1/monitors \ + --header '' \ +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "Monitors found", + "data": [ + { + "_id": "664d070786e62625ac612ca1", + "userId": "6645079aae0b439371913972", + "name": "Wha3", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "createdAt": "2024-05-21T20:41:43.051Z", + "updatedAt": "2024-05-21T20:45:10.496Z", + "__v": 0 + }, + { + "_id": "664e5ccf189c864800debc16", + "userId": "6645079aae0b439371913972", + "name": "Inserting a new Monitor", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "createdAt": "2024-05-22T20:59:59.295Z", + "updatedAt": "2024-05-22T20:59:59.295Z", + "__v": 0 + } + ] +} +``` + --- @@ -256,6 +289,35 @@ curl --request POST \ > | --------- | --------------------------------------------------- | > | `Monitor` | Single monitor with the id in the request parameter | +###### Sample cURL Request + +``` +curl --request GET \ + --url http://localhost:5000/api/v1/monitors/664d070786e62625ac612ca1 \ + --header '' \ +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "Monitor found", + "data": { + "_id": "664d070786e62625ac612ca1", + "userId": "6645079aae0b439371913972", + "name": "My Monitor", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "createdAt": "2024-05-21T20:41:43.051Z", + "updatedAt": "2024-05-21T20:45:10.496Z", + "__v": 0 + } +} +``` + --- @@ -269,14 +331,197 @@ curl --request POST \ > | -------------- | ----- | > | Method | GET | -###### Request Payload +###### Response Payload > | Type | Notes | > | ---------------- | ------------------------------------------------------------------ | > | `Array[Monitor]` | Array of monitors created by user with userId specified in request | +###### Sample cURL Request + +``` +curl --request GET \ + --url http://localhost:5000/api/v1/monitors/user/6645079aae0b439371913972 \ + --header '' \ +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "Monitors for user 6645079aae0b439371913972 found", + "data": [ + { + "_id": "664d070786e62625ac612ca1", + "userId": "6645079aae0b439371913972", + "name": "Wha3", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "createdAt": "2024-05-21T20:41:43.051Z", + "updatedAt": "2024-05-21T20:45:10.496Z", + "__v": 0 + }, + { + "_id": "664e5ccf189c864800debc16", + "userId": "6645079aae0b439371913972", + "name": "Inserting a new Monitor", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "createdAt": "2024-05-22T20:59:59.295Z", + "updatedAt": "2024-05-22T20:59:59.295Z", + "__v": 0 + } + ] +} +``` + +--- + +
+POST/api/v1/monitors + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | POST | + +###### Response Payload + +> | Type | Notes | +> | --------- | --------------------------------- | +> | `Monitor` | Newly created monitor is returned | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/monitors \ + --header \ + --header 'Content-Type: application/json' \ + --data '{"userId": "6645079aae0b439371913972", + "name": "Inserting a new Monitor", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000}' +``` + +##### Sample Response + +```json +{ + "success": true, + "msg": "Monitor created", + "data": { + "userId": "6645079aae0b439371913972", + "name": "Inserting a new Monitor", + "description": "Description", + "url": "https://monitor0.com", + "isActive": true, + "interval": 60000, + "_id": "664e5ccf189c864800debc16", + "createdAt": "2024-05-22T20:59:59.295Z", + "updatedAt": "2024-05-22T20:59:59.295Z", + "__v": 0 + } +} +``` + +
+ +--- + +
+POST/api/v1/monitors/delete/{monitorId} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | POST | + +###### Response Payload + +> | Type | Notes | +> | ---- | ------------------- | +> | None | No payload returned | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/monitors/delete/664e632a7a3ee9d620761938 \ + --header '' \ + --header 'Content-Type: application/json' \ +``` + +##
+ +--- + +
+POST/api/v1/monitors/edit/{monitorId} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | POST | + +###### Response Payload + +> | Type | Notes | +> | --------- | --------------------------- | +> | `Monitor` | Returns the updated monitor | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/monitors/edit/664e5ccf189c864800debc16 \ + --header ' diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 90a12e244..1285726e1 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -57,7 +57,7 @@ const registerController = async (req, res) => { service: SERVICE_NAME, userId: newUser._id, }); - const token = issueToken(newUser); + const token = issueToken(newUser._doc); return res .status(200) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 3a46427c4..cf245fba3 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -41,8 +41,7 @@ const getMonitorById = async (req, res) => { } try { - const monitorId = req.params.monitorId; - const monitor = await req.db.getMonitorById(monitorId); + const monitor = await req.db.getMonitorById(req, res); return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { logger.error(error.message, { service: "monitor" }); @@ -68,7 +67,7 @@ const getMonitorsByUserId = async (req, res) => { try { const userId = req.params.userId; - const monitors = await req.db.getMonitorsByUserId(userId); + const monitors = await req.db.getMonitorsByUserId(req, res); logger.info(`Monitors for user ${userId} found`, { service: "monitor", userId: userId, From 54c94879d7115001c902d34bf754c097cb5b8ad8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 22 May 2024 14:43:07 -0700 Subject: [PATCH 09/10] Changed delete status code to 200 to allow for consistent API response, Updated docs to reflect this --- README.md | 11 ++++++++++- Server/controllers/monitorController.js | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d362d5c3..44a30ea94 100644 --- a/README.md +++ b/README.md @@ -454,7 +454,7 @@ curl --request POST \ > | ---- | ------------------- | > | None | No payload returned | -##### Sample CURL request +###### Sample CURL request ``` curl --request POST \ @@ -463,6 +463,15 @@ curl --request POST \ --header 'Content-Type: application/json' \ ``` +###### Sample Response + +```json +{ + "success": true, + "msg": "Monitor deleted" +} +``` + ##
--- diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index cf245fba3..f004f98f3 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -135,7 +135,7 @@ const deleteMonitor = async (req, res) => { * when it is deleted so there is no orphaned data * We also need to make sure to stop all running services for this monitor */ - return res.status(204).json({ success: true, msg: "Monitor deleted" }); + return res.status(200).json({ success: true, msg: "Monitor deleted" }); } catch (error) { logger.error(error.message, { service: "monitor" }); return res.status(500).json({ success: false, msg: error.message }); From f7b0ccc9ccfc045f98e645e7375f7a2d08e6d4f2 Mon Sep 17 00:00:00 2001 From: M M Date: Thu, 23 May 2024 16:57:58 -0700 Subject: [PATCH 10/10] Implemented the navbar dropdown section for the user. --- Client/src/Components/NavBar/index.jsx | 52 ++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/Client/src/Components/NavBar/index.jsx b/Client/src/Components/NavBar/index.jsx index f957537f2..c9f08a292 100644 --- a/Client/src/Components/NavBar/index.jsx +++ b/Client/src/Components/NavBar/index.jsx @@ -1,10 +1,48 @@ -import React from 'react' -import './index.css' +import { useState } from 'react'; +import './index.css'; +import Button from '@mui/material/Button'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; const NavBar = () => { - return ( -
NavBar Component
- ) -} + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); -export default NavBar \ No newline at end of file + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( +
+ + + Profile + Team + Invite Colleagues + Log Out + +
+ ); +}; + +export default NavBar;