Merge pull request #54 from bluewave-labs/feat/jwt

Feat/jwt resolves #41
This commit is contained in:
Veysel authored and GitHub committed 2024-05-22 15:37:51 -04:00
commit 2ffe422b0d
8 files changed
+196 -48

No files matched your search

+15 -24
View File
@@ -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"
}
```
+38 -20
View File
@@ -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);
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<Express.Response>}
* @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 });
}
};
-1
View File
@@ -5,7 +5,6 @@ const {
} = require("../validation/joi");
const logger = require("../utils/logger");
const Monitor = require("../models/Monitor");
/**
* Returns all monitors
+3 -1
View File
@@ -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;
}
+3 -2
View File
@@ -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) => {
+42
View File
@@ -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 };
+94
View File
@@ -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",
+1
View File
@@ -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"