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/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/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; 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 diff --git a/README.md b/README.md index 92e1b7bb1..44a30ea94 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,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 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 | | --- @@ -130,9 +131,9 @@ Example: ##### Response Payload -> | Type | Notes | -> | ------ | ---------- | -> | `User` | User model | +> | Type | Notes | +> | ---- | ---------------------------------- | +> | JWT | JSON Web Token containing a `User` | ##### Sample CURL request @@ -153,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": "" } ``` @@ -191,9 +182,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 +204,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": "" } ``` @@ -246,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 + } + ] +} +``` + --- @@ -265,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 + } +} +``` + --- @@ -278,16 +331,210 @@ 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' \ +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "Monitor deleted" +} +``` + +##
+ +--- + +
+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 ' + +Made with [contrib.rocks](https://contrib.rocks). diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index e8fdc739e..1285726e1 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,7 +1,19 @@ 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"); +const SERVICE_NAME = "auth"; + +/** + * 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 @@ -23,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 }); } @@ -41,20 +53,29 @@ 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._doc); + 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" }); + logger.error(error.message, { service: SERVICE_NAME }); 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,25 +90,22 @@ 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!"); } - // 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; - // Happy path, return user - // In the future we'll probably return a JWT instead of a user + // Happy path, return token + const token = issueToken(userWithoutPassword); 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" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 8b2f72ab1..f004f98f3 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 @@ -42,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" }); @@ -69,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, @@ -137,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 }); 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/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/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 }; 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"