diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..56ffea5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +*.env +.env +.env.* + +node_modules +node_modules/** + +./dist +dist + +./coverage +coverage + +./test +test + diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..fff6d9f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": false, + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "endOfLine": "crlf" +} \ No newline at end of file diff --git a/README.md b/README.md index 4cf3de2..af9ce83 100644 --- a/README.md +++ b/README.md @@ -6,54 +6,79 @@

-**v1.0.2** +**v1.0.3** -This app is build using Backend Development tools like -> **bcrypt, body-parser, dotenv, express, jsonwebtoken, mongoose, node-rest-client, path, moment-timezone** +The Social App is built using the following backend development tools: -### API's available:- +- bcrypt +- body-parser +- dotenv +- express +- jsonwebtoken +- mongoose +- node-rest-client +- path +- moment-timezone +- prettier +- helmet +- express-rate-limit -NOTE:- -**1ī¸âƒŖ** Use {your_URL} or {localhost} -**2ī¸âƒŖ** Request Format - (Method) (Type) (URL) +## APIs Available -1. **[POST]** - Signup - http://{URL}/api/v1/auth/signup -2. **[POST]** - Signin - http://{URL}/api/v1/auth/signin -3. **[POST]** - Add one Post/many Posts (one Post at a time) - http://{URL}/api/v1/addPost -4. **[GET]** - Fetch all Posts specifying user - http://{URL}/api/v1/posts -5. **[DELETE]** - Delete one Post specifying Post Id - http://{URL}/api/v1/posts -6. **[DELETE]** - Delete All Post specifying userId - http://{URL}/api/v1/allposts +The Social App provides the following APIs: + +1. **[POST]** - Signup - `http://{URL}/api/v1/auth/signup` +2. **[POST]** - Signin - `http://{URL}/api/v1/auth/signin` +3. **[POST]** - Add one Post/many Posts (one Post at a time) - `http://{URL}/api/v1/addPost` +4. **[GET]** - Fetch all Posts specifying user - `http://{URL}/api/v1/posts` +5. **[DELETE]** - Delete one Post specifying Post Id - `http://{URL}/api/v1/posts` +6. **[DELETE]** - Delete All Post specifying userId - `http://{URL}/api/v1/allposts` + +Note: Replace `{URL}` with your actual URL. ### Installation:- - Run this command to install dependencies - ### ```npm install``` + ### `npm install` + + **NOTE:** If above command doesn't install all dependencies, run below command - **NOTE:** If above command doesn't install all dependencies, run below command - ### ```npm run build``` + ### `npm run build` - Start the server by running this command - ### ```npm run start``` -- To check if all is working good! 🎉 - http://{URL}:{PORT} -- **N.B.** - **PORT** can be defined in **".env"** file, otherwise it will default to **3001** + ### `npm run start` +- To check if all is working good! 🎉 - http://{URL}:{SERVER_PORT} +- **N.B.** - **SERVER_PORT** can be defined in **".env"** file, otherwise it will default to **3001**
+ ### Note to Developers:- + - I have added my MongoDB database URL to my Environment Variable **(.env)** file and all other secrets, so I insist to create a **(.env)** file and add necessary configuration data which is needed to be hidden from end-user/other developers. - All the API's mentioned above will work best in Postman (Preferred, becoz I use it!) for testing and development, but other apps may also be used. ### Update -***30-05-2023*** + +**_30-05-2023_** + - Testing Backend API's and other features to optimise it. Need a Front-end part to be build. -***01-06-2023*** -- Added some functionalities. +**_01-06-2023_** + +- Added some functionalities. + +**_27-09-2023_** + +- Added some functionalities. +- Updated README + +**_12-01-2024_** -***27-09-2023*** -- Added some functionalities. -- Updated README - +- Added helmet for secured headers +- Added api rate limit for security +- Added CodeQl, Node.js Vulnerability Check for security +- Implemented Prettier for uniform code formatting and readability throughout the project diff --git a/assets/home_page.jpg b/assets/home_page.jpg index 99aa21d..f2c2c79 100644 Binary files a/assets/home_page.jpg and b/assets/home_page.jpg differ diff --git a/configs/server.config.js b/configs/server.config.js index f177a58..0251e0e 100644 --- a/configs/server.config.js +++ b/configs/server.config.js @@ -1,6 +1,6 @@ -require('dotenv').config() +require("dotenv").config(); module.exports = { - PORT : process.env.PORT || 3001, - SECRET : process.env.SECRET_KEY -} \ No newline at end of file + SERVER_PORT: process.env.SERVER_PORT || 3001, + JWT_SECRET_KEY: process.env.JWT_SECRET_KEY, +}; diff --git a/controllers/auth.controller.js b/controllers/auth.controller.js index 1dfff94..140427c 100644 --- a/controllers/auth.controller.js +++ b/controllers/auth.controller.js @@ -3,197 +3,199 @@ const bcrypt = require("bcrypt"); const User = require("../models/user.model.js"); const Post = require("../models/post.model.js"); const jwt = require("jsonwebtoken"); -const { SECRET } = require("../configs/server.config.js"); +const { JWT_SECRET_KEY } = require("../configs/server.config.js"); const { formatDate } = require("../utils/formatDate.js"); //to convert & view UTC date to Indian Time format (doesn't modify in MongoDB database) /************** SIGNUP/REGISTER API ********************/ exports.signup = async (req, res) => { - try { - // get the data from request body - let { name, userId, email, password } = req.body; - - /************ GENERATE HASH FOR PASSWORD *******************/ - const hash = await bcrypt.hash(password, 10); - - /************* STORE USER DATA TO DB *************/ - const userCreated = await User.create({ - name, - userId, - email, - password:hash - }); - //CONVERT JSON DATE TO INDIA DATE FORMAT - const IndiaDateCreatedAt = formatDate(userCreated.createdAt); - //Displays the user created - console.log(userCreated); - - /************* SEND RESPONSE TO USER ************/ - res.status(201).send(['Message: User created Successfully', - { Name: userCreated.name, - UserID: userCreated.userId, - Email: userCreated.email, - Created_At: IndiaDateCreatedAt - }]); - - } catch (err) { - console.log("Error Occured!", err.message); - res.status(500).send("Internal Server Error:"); - } + // get the data from request body + let { name, userId, email, password } = req.body; + + /************ GENERATE HASH FOR PASSWORD *******************/ + const hash = await bcrypt.hash(password, 10); + + /************* STORE USER DATA TO DB *************/ + const userCreated = await User.create({ + name, + userId, + email, + password: hash, + }); + //CONVERT JSON DATE TO INDIA DATE FORMAT + const IndiaDateCreatedAt = formatDate(userCreated.createdAt); + //Displays the user created + console.log(userCreated); + + /************* SEND RESPONSE TO USER ************/ + res + .status(201) + .send([ + "Message: User created Successfully", + { + Name: userCreated.name, + UserID: userCreated.userId, + Email: userCreated.email, + Created_At: IndiaDateCreatedAt, + }, + ]); + } catch (err) { + console.log("Error Occured!", err.message); + res.status(500).send("Internal Server Error:"); + } }; /************** SIGNIN API ****************************/ exports.signin = async (req, res) => { - try { - // GET DATA FROM REQUEST BODY - const { userId, password } = req.body; - - /*** CHECK FOR USERID IS PROVIDED IN REQUEST BODY OR NOT ********************/ - if (!userId) { - console.log("\nuserId is not provided"); - return res.status(400).send("Bad Request! UserId not provided"); - } - - //FETCH REQUESTED USER FROM DB - const user = await User.findOne({ userId: userId}); - - /**** CHECK FOR USER EXISTS IN DB OR NOT *********** */ - if (!user) { - console.log("UserID doesn't exists"); - return res.status(400).send("UserId doesn't exist in our server"); - } - - /*** CHECK FOR PASSWORD IS PROVIDED IN REQUEST BODY OR NOT ********* */ - if (!password) { - console.log("Password not entered"); - return res.status(400).send("Please enter the password"); - } - - /******* CHECK WHETHER PASSWORD IS MATCHING IN DB OR NOT ***********/ - let isCorrectPassword = await bcrypt.compare(password,user.password); - /******* CHECK WHETHER PASSWORD IS CORRECT OR NOT ***********/ - if (!isCorrectPassword) { - console.log("Invalid Password"); - return res.status(401).send("Invalid Password"); - } - - /***** CREATE A ACCESS TOKEN FOR THE USER ************ */ - let token = jwt.sign({ userId: userId }, SECRET, { - expiresIn: process.env.JWT_EXPIRY, - }); - - /**** CONVERT JSON DATE IN DB TO INDIA DATE FORMAT ***********/ - const IndiaDateCreatedAt = formatDate(user.createdAt); - const IndiaDateUpdatedAt = formatDate(user.updatedAt); - /**** CREATE A USER OBJECT TO STRUCTURE THE DATA TO SEND TH RESPONSE TO USER *****/ - const userData = { - name: user.name, - userId: user.userId - }; - - /***** SEND USER DATA IN RESPONSE FOR SIGNIN REQUESTED ********/ - console.log("SignIn Req for:", userData); - /***** SEND ACCESS TOKEN TO USER FOR SIGNIN *******/ - res.status(200).send({ - Message: "Signed in Successfully!", - AccessToken: token, - }); - } catch (error) { - console.log("Error occured", error); - res.status(500).send("Internal Server Error"); - } + // GET DATA FROM REQUEST BODY + const { userId, password } = req.body; + + /*** CHECK FOR USERID IS PROVIDED IN REQUEST BODY OR NOT ********************/ + if (!userId) { + console.log("\nuserId is not provided"); + return res.status(400).send("Bad Request! UserId not provided"); + } + + //FETCH REQUESTED USER FROM DB + const user = await User.findOne({ userId: userId }); + + /**** CHECK FOR USER EXISTS IN DB OR NOT *********** */ + if (!user) { + console.log("UserID doesn't exists"); + return res.status(400).send("UserId doesn't exist in our server"); + } + + /*** CHECK FOR PASSWORD IS PROVIDED IN REQUEST BODY OR NOT ********* */ + if (!password) { + console.log("Password not entered"); + return res.status(400).send("Please enter the password"); + } + + /******* CHECK WHETHER PASSWORD IS MATCHING IN DB OR NOT ***********/ + let isCorrectPassword = await bcrypt.compare(password, user.password); + /******* CHECK WHETHER PASSWORD IS CORRECT OR NOT ***********/ + if (!isCorrectPassword) { + console.log("Invalid Password"); + return res.status(401).send("Invalid Password"); + } + + /***** CREATE A ACCESS TOKEN FOR THE USER ************ */ + let token = jwt.sign({ userId: userId }, JWT_SECRET_KEY, { + expiresIn: process.env.JWT_EXPIRY, + }); + + /**** CONVERT JSON DATE IN DB TO INDIA DATE FORMAT ***********/ + const IndiaDateCreatedAt = formatDate(user.createdAt); + const IndiaDateUpdatedAt = formatDate(user.updatedAt); + /**** CREATE A USER OBJECT TO STRUCTURE THE DATA TO SEND TH RESPONSE TO USER *****/ + const userData = { + // name: user.name, + userId: user.userId, + }; + + /***** SEND USER DATA IN RESPONSE FOR SIGNIN REQUESTED ********/ + console.log("SignIn Req for:", userData); + /***** SEND ACCESS TOKEN TO USER FOR SIGNIN *******/ + res.status(200).send({ + Message: "Signed in Successfully!", + AccessToken: token, + }); + } catch (error) { + console.log("Error occured", error); + res.status(500).send("Internal Server Error"); + } }; /************** CHANGE PASSWORD API*********************/ exports.changePassword = async (req, res) => { try { - // GET DATA FROM REQUEST BODY - const { userId, password } = req.body; - - //FETCH REQUESTED USER FROM DB - const userCheck = await User.findOne({ - userId: userId, - }); - - /**** CHECK FOR USER EXISTS IN DB OR NOT *********** */ - if (!userCheck) { - console.log("UserID doesn't exists"); - return res.status(400).send("UserId doesn't exist in our server"); - } - - /*** CHECK FOR PASSWORD TO CHANGE, IS PROVIDED IN REQUEST BODY OR NOT ********* */ - if (!password) { - console.log("Password not entered"); - return res.status(400).send("Please enter the password"); - } - - /************ GENERATE HASH FOR PASSWORD *******************/ - const hash = await bcrypt.hash(password, 10); - - /************ FIND THE USER, CHANGE PASSWORD, & UPDATE IN DB *******************/ - await User.findOneAndUpdate( - { - userId: userId, - }, - { - password: hash, - updatedAt: Date.now(), - } - ).exec(); - - /*** LOG FOR SUCCESSFULL PASSWORD CHANGE ****/ - console.log(`Password for user '${userId}' updated!`); - /*** SEND RESPONSE TO USER FOR SUCCESSFULL PASSWORD CHANGE ****/ - res.status(200).send(`Password for user '${userId}' updated!`); - - } catch (err) { - console.log("Error while updating the password", err.message); - res.status(500).send("Some internal error occured"); + // GET DATA FROM REQUEST BODY + const { userId, password } = req.body; + + //FETCH REQUESTED USER FROM DB + const userCheck = await User.findOne({ + userId: userId, + }); + + /**** CHECK FOR USER EXISTS IN DB OR NOT *********** */ + if (!userCheck) { + console.log("UserID doesn't exists"); + return res.status(400).send("UserId doesn't exist in our server"); + } + + /*** CHECK FOR PASSWORD TO CHANGE, IS PROVIDED IN REQUEST BODY OR NOT ********* */ + if (!password) { + console.log("Password not entered"); + return res.status(400).send("Please enter the password"); + } + + /************ GENERATE HASH FOR PASSWORD *******************/ + const hash = await bcrypt.hash(password, 10); + + /************ FIND THE USER, CHANGE PASSWORD, & UPDATE IN DB *******************/ + await User.findOneAndUpdate( + { + userId: userId, + }, + { + password: hash, + updatedAt: Date.now(), } + ).exec(); + + /*** LOG FOR SUCCESSFULL PASSWORD CHANGE ****/ + console.log(`Password for user '${userId}' updated!`); + /*** SEND RESPONSE TO USER FOR SUCCESSFULL PASSWORD CHANGE ****/ + res.status(200).send(`Password for user '${userId}' updated!`); + } catch (err) { + console.log("Error while updating the password", err.message); + res.status(500).send("Some internal error occured"); + } }; /************** DELETE USER API *********************/ exports.deleteUser = async (req, res) => { try { - /**** GET USERID & PASSWORD FROM REQUEST BODY ********/ - const {userId, password} = req.body; - /**** CHECK FOR USERID & PASSWORD PROVIDED BY USER OR NOT ********/ - if (!userId || !password) { - console.log("UserID/Password not provided"); - throw new Error("UserID/Password not provided"); - } - - //find the User in DB from requested userId - const user = await User.findOne({ - userId: userId, - }); - - /******** CHECK WHETHER USER IN OUR DB OR NOT ***************/ - if(user==null) { - console.log('User not found in DB/server'); - return res.status(400).send('Requested user is not in our Server!') - } - - //check whether password provided is correct or not - let isPasswordValid = await bcrypt.compare(password, user.password); - if (isPasswordValid != true) throw new Error("Password not correct"); - - //check posts with userId provided in DB - await Post.find({ user: userId }); - - //deletes all posts of provided userId - Post.deleteMany({ user: userId }).exec(); - - //deletes the user with provided userId - User.findOneAndDelete({ userId: userId }).exec(); - - console.log(`User with userId '${userId}' and all it's data are deleted`); - /******** SEND RESPONSE TO USER ABOUT DELETION **************/ - res.status(200).send(`User with userId '${userId}' and all it's data are deleted`); - - } catch (err) { - console.log("Error: ", err.message); - res.status(400).send(err.message); - } + /**** GET USERID & PASSWORD FROM REQUEST BODY ********/ + const { userId, password } = req.body; + /**** CHECK FOR USERID & PASSWORD PROVIDED BY USER OR NOT ********/ + if (!userId || !password) { + console.log("UserID/Password not provided"); + throw new Error("UserID/Password not provided"); + } + + //find the User in DB from requested userId + const user = await User.findOne({ + userId: userId, + }); + + /******** CHECK WHETHER USER IN OUR DB OR NOT ***************/ + if (user == null) { + console.log("User not found in DB/server"); + return res.status(400).send("Requested user is not in our Server!"); + } + + //check whether password provided is correct or not + let isPasswordValid = await bcrypt.compare(password, user.password); + if (isPasswordValid != true) throw new Error("Password not correct"); + + //check posts with userId provided in DB + await Post.find({ user: userId }); + + //deletes all posts of provided userId + Post.deleteMany({ user: userId }).exec(); + + //deletes the user with provided userId + User.findOneAndDelete({ userId: userId }).exec(); + + console.log(`User with userId '${userId}' and all it's data are deleted`); + /******** SEND RESPONSE TO USER ABOUT DELETION **************/ + res + .status(200) + .send(`User with userId '${userId}' and all it's data are deleted`); + } catch (err) { + console.log("Error: ", err.message); + res.status(400).send(err.message); + } }; diff --git a/controllers/post.controllers.js b/controllers/post.controllers.js index e1b0395..6f6fc4f 100644 --- a/controllers/post.controllers.js +++ b/controllers/post.controllers.js @@ -1,94 +1,93 @@ -require('dotenv').config() -const Post = require('../models/post.model'); -const User = require('../models/user.model') -const jwt = require('jsonwebtoken'); -const { SECRET } = require('../configs/server.config.js') -const { formatDate } = require("../utils/formatDate") //to convert & view UTC date to Indian Time format (doesn't modify in MongoDB database) +require("dotenv").config(); +const Post = require("../models/post.model"); +const User = require("../models/user.model"); +const jwt = require("jsonwebtoken"); +const { JWT_SECRET_KEY } = require("../configs/server.config.js"); +const { formatDate } = require("../utils/formatDate"); //to convert & view UTC date to Indian Time format (doesn't modify in MongoDB database) /********* STORE POST TO DB REQUESTED BY USER *********/ exports.addPost = async (req, res) => { - try { - // Extract the post data from the request body - const { title, content } = req.body; - // Verify that the user is authenticated by checking the JWT token in the Authorization header - let token = req.headers['x-access-token']; - - const decoded = jwt.verify(token, SECRET); - // console.log(decoded); - const user = await User.findOne({ userId: decoded.userId }); - - //check if title of post is already in DB or not - const titleCheck = await Post.findOne({ title: title }) - - if(titleCheck) { - console.log("Title already in DB, create unique title"); - return res.status(409).send("Title already in DB, create unique title") - } - - // Create a new post with the user ID included - const createPost = new Post({ - title, - content - }); - - // Save the post to the database - await createPost.save(); - console.log("Post created successfully", createPost); - res.status(201).send({Message: "Post created successfully",createPost}); - - } catch (error) { - console.log("Error at post.controller:", error.message); - res.status(500).send('Internal Server Error'); + try { + // Extract the post data from the request body + const { title, content } = req.body; + // Verify that the user is authenticated by checking the JWT token in the Authorization header + let token = req.headers["x-access-token"]; + + const decoded = jwt.verify(token, JWT_SECRET_KEY); + // console.log(decoded); + const user = await User.findOne({ userId: decoded.userId }); + + //check if title of post is already in DB or not + const titleCheck = await Post.findOne({ title: title }); + + if (titleCheck) { + console.log("Title already in DB, create unique title"); + return res.status(409).send("Title already in DB, create unique title"); } + + // Create a new post with the user ID included + const createPost = new Post({ + title, + content, + }); + + // Save the post to the database + await createPost.save(); + console.log("Post created successfully", createPost); + res.status(201).send({ Message: "Post created successfully", createPost }); + } catch (error) { + console.log("Error at post.controller:", error.message); + res.status(500).send("Internal Server Error"); + } }; exports.getPostByUserId = async (req, res) => { - try { - //Verify that the user is authenticated - //by checking the JWT token in the Authorization header - const token = req.headers['x-access-token']; - - const decoded = jwt.verify(token, SECRET); - // console.log(decoded); - // console.log("decoded.id", decoded.userId); - - if(!req.body.userId) throw new Error //if no id is provided - - if(decoded.userId != req.body.userId){ - console.log("Unauthorized User/User not in our Server"); - return res.status(401).send('Unauthorized User!'); - } - - const user = await User.findOne({ userId: decoded.userId }); - - const posts = await Post.find({ - user: user._id - }); - - // Check if any posts were found - if (posts.length === 0) { - console.log("No posts found"); - return res.status(404).send('Post/Posts not found!'); - } - - // Return the found posts - let allPostData = []; - posts.forEach(element => { - const IndiaDateCreatedAt = formatDate(element.createdAt) - const IndiaDateUpdatedAt = formatDate(element.updatedAt) - let postData = { - id: element._id, - title: element.title, - content: element.content, - createdAt: IndiaDateCreatedAt, - updatedAt: IndiaDateUpdatedAt - } - allPostData.push(postData); - }); - console.log(allPostData); - res.status(200).send(allPostData); - // const IndiaDateCreatedAt = formatDate(posts.createdAt) - // const IndiaDateUpdatedAt = formatDate(posts.updatedAt) + try { + //Verify that the user is authenticated + //by checking the JWT token in the Authorization header + const token = req.headers["x-access-token"]; + + const decoded = jwt.verify(token, JWT_SECRET_KEY); + // console.log(decoded); + // console.log("decoded.id", decoded.userId); + + if (!req.body.userId) throw new Error(); //if no id is provided + + if (decoded.userId != req.body.userId) { + console.log("Unauthorized User/User not in our Server"); + return res.status(401).send("Unauthorized User!"); + } + + const user = await User.findOne({ userId: decoded.userId }); + + const posts = await Post.find({ + user: user._id, + }); + + // Check if any posts were found + if (posts.length === 0) { + console.log("No posts found"); + return res.status(404).send("Post/Posts not found!"); + } + + // Return the found posts + let allPostData = []; + posts.forEach((element) => { + const IndiaDateCreatedAt = formatDate(element.createdAt); + const IndiaDateUpdatedAt = formatDate(element.updatedAt); + let postData = { + id: element._id, + title: element.title, + content: element.content, + createdAt: IndiaDateCreatedAt, + updatedAt: IndiaDateUpdatedAt, + }; + allPostData.push(postData); + }); + console.log(allPostData); + res.status(200).send(allPostData); + // const IndiaDateCreatedAt = formatDate(posts.createdAt) + // const IndiaDateUpdatedAt = formatDate(posts.updatedAt) // const postData = { // id: posts._id, // title: posts.title, @@ -98,64 +97,60 @@ exports.getPostByUserId = async (req, res) => { // // updatedAt: IndiaDateUpdatedAt // } // // res.status(200).send(postData); - - } catch (err) { - console.error("Internal Error:",err.message); - res.status(500).send("Internal Server Error"); - } + } catch (err) { + console.error("Internal Error:", err.message); + res.status(500).send("Internal Server Error"); + } }; exports.deletePostByPostTitle = async (req, res) => { - const titleReq = req.query.title - try { - if(!titleReq) throw new Error("Title of post not provided"); + const titleReq = req.query.title; + try { + if (!titleReq) throw new Error("Title of post not provided"); + + const post = await Post.findOneAndDelete({ title: titleReq }).exec(); + + if (post == null) throw new Error("Post is null/Post is not in server"); + + console.log(`Post Deleted for Title: "${titleReq}"`); + return res + .status(200) + .send(`Post Deleted Successfully! for Title: "${titleReq}"`); + } catch (err) { + console.log("Error deleting post: ", err.message); + return res.status(500).send("Internal Server Error"); + } +}; - const post = await Post.findOneAndDelete({ title : titleReq }).exec() +exports.deleteAllPostsByUserId = async (req, res) => { + try { + //Verify that the user is authenticated by checking the + //JWT token in the Authorization header + const token = req.headers["x-access-token"]; + const decoded = jwt.verify(token, JWT_SECRET_KEY); - if(post == null) throw new Error("Post is null/Post is not in server"); + const user = await User.findOne({ + userId: decoded.id, + }); - console.log(`Post Deleted for Title: "${titleReq}"`); - return res.status(200).send(`Post Deleted Successfully! for Title: "${titleReq}"`) - - } - catch (err) - { - console.log("Error deleting post: ", err.message); - return res.status(500).send("Internal Server Error"); - } + const userId = user._id.toString(); //convert ObjectId to String -} + const posts = await Post.find({ + user: userId, + }); -exports.deleteAllPostsByUserId = async (req, res) => { - try { - //Verify that the user is authenticated by checking the - //JWT token in the Authorization header - const token = req.headers['x-access-token']; - const decoded = jwt.verify(token, SECRET); - - const user = await User.findOne({ - userId: decoded.id - }) - - const userId = user._id.toString() //convert ObjectId to String - - const posts = await Post.find({ - user: userId - }) - - if(posts.length === 0) { - console.log(`No posts with user: ${user.name}`); - return res.status(200).send(`No posts with user: ${user.name}`) - } - Post.deleteMany({ user : userId}).exec() - console.log(`All Posts of '${user.name}' with Id: ${userId} are deleted`); - return res.status(200).send(`All Posts of '${user.name}' are deleted`) - } catch(err) { - console.log("Error deleting posts", err); - return res.status(500).send("Internal Server Error") + if (posts.length === 0) { + console.log(`No posts with user: ${user.name}`); + return res.status(200).send(`No posts with user: ${user.name}`); } -} - + Post.deleteMany({ user: userId }).exec(); + console.log(`All Posts of '${user.name}' with Id: ${userId} are deleted`); + return res.status(200).send(`All Posts of '${user.name}' are deleted`); + } catch (err) { + console.log("Error deleting posts", err); + return res.status(500).send("Internal Server Error"); + } +}; /*** TEST DATA USING AXIOS CONTROLLER**** */ // exports.getData = async (req, res) => { @@ -167,4 +162,4 @@ exports.deleteAllPostsByUserId = async (req, res) => { // console.log('Error:', error); // res.status(500).send('Internal Error') // } -// } \ No newline at end of file +// } diff --git a/index.js b/index.js index 1147304..55f12c0 100644 --- a/index.js +++ b/index.js @@ -1,49 +1,50 @@ -require('dotenv').config() //needed to fetch data from .env file -const express = require('express') -const mongoose = require('mongoose') -const userIP = require('user-ip'); -const cookieParser = require('cookie-parser'); -const securedHeaders = require('helmet'); - -const { PORT } = require('./configs/server.config.js') -const app = express() -const db_url = process.env.DB_URL || `mongodb://127.0.0.1:27017/${process.env.DB_NAME}` - -app.use(express.urlencoded({extended:true})); +require("dotenv").config(); //needed to fetch data from .env file +const express = require("express"); +const mongoose = require("mongoose"); +const userIP = require("user-ip"); +const cookieParser = require("cookie-parser"); +const securedHeaders = require("helmet"); +const { limiter } = require("./utils/api-rate-limiter.js"); + +const { SERVER_PORT } = require("./configs/server.config.js"); +const app = express(); +const db_url = + process.env.DB_URL || `mongodb://127.0.0.1:27017/${process.env.DB_NAME}`; + +app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser()); app.use(securedHeaders()); +app.use(limiter); // connect to MongoDB // Event handlers for successful connection and connection error const connectDB = async () => { - try { console.time('Mongodb connection time:'); - const connect = await mongoose.connect(db_url,{ - // useNewUrlParser: true, // DEPRECATED - // useUnifiedTopology: true // DEPRECATED - }); - console.timeEnd('Mongodb connection time:'); - console.log(`MongoDB Connected to Host: ${connect.connection.host}`); - } catch (error) { - console.timeEnd(); - console.log("Can't connect to DB:", error.message); - } -} + console.time("Mongodb connection time:"); + const connect = await mongoose.connect(db_url, { + // useNewUrlParser: true, // DEPRECATED + // useUnifiedTopology: true // DEPRECATED + }); + console.timeEnd("Mongodb connection time:"); + console.log(`MongoDB Connected to Host: ${connect.connection.host}`); +}; // FIRST CONNECT TO MONGODB THEN START LISTENING TO REQUESTS -connectDB().then(() => { - app.listen(PORT, () => { - console.log(`Listening all requests on port ${PORT}`); +connectDB() + .then(() => { + app.listen(SERVER_PORT, () => { + console.log(`Listening all requests on port ${SERVER_PORT}`); + }); }) -}).catch((e)=>console.log(e)) // IF DB CONNECT FAILED, CATCH ERROR - + .catch((e) => { + console.log("Can't connect to DB:", e.message); // IF DB CONNECT FAILED, CATCH ERROR + }); /**************************HOME PAGE**************************** */ -app.get('/', (req, res) => { - res.status(200).send(`

Backend Running! 🎉

`) +app.get("/", (req, res) => { + res.status(200).send(`

Backend Running! 🎉

`); }); /************** IMPORT API's ********** */ require("./routes/auth.routes")(app); require("./routes/post.routes")(app); - diff --git a/middlewares/AuthJWT.js b/middlewares/AuthJWT.js index f334a05..0902988 100644 --- a/middlewares/AuthJWT.js +++ b/middlewares/AuthJWT.js @@ -1,33 +1,30 @@ const jwt = require("jsonwebtoken"); -const { SECRET } = require('../configs/server.config.js') +const { JWT_SECRET_KEY } = require("../configs/server.config.js"); /***** VERIFY ACCESS TOKEN TO CHECK IF TOKEN IS PRESENT AND VALID ********/ let verifyToken = (req, res, next) => { + /*** GET THE ACCESS TOKEN FORM HEADER *****/ + let token = req.headers["x-access-token"]; - /*** GET THE ACCESS TOKEN FORM HEADER *****/ - let token = req.headers['x-access-token']; - - /*** CHECK WHETHER ACCESS TOKEN PROVIDED OR NOT *****/ - if (!token) { - console.log("No token provided 😔") - return res.status(403).send("No Token Provided"); + /*** CHECK WHETHER ACCESS TOKEN PROVIDED OR NOT *****/ + if (!token) { + console.log("No token provided 😔"); + return res.status(403).send("No Token Provided"); + } + /*** VERIFY ACCESS TOKEN FOR THE USER *****/ + jwt.verify(token, JWT_SECRET_KEY, (err, decoded) => { + // Checks for any error in JWT verification + if (err) { + console.log("Error --> Token Expired", err.name); + return res.status(401).send("Token Expired!"); + } else { + /****** USER FETCHED FROM TOKEN ASSIGNED TO REQ.USERID ********/ + req.userId = decoded.userId; + next(); } - /*** VERIFY ACCESS TOKEN FOR THE USER *****/ - jwt.verify(token, SECRET, (err, decoded) => { - // Checks for any error in JWT verification - if (err) { - console.log("Error:", err.message); - return res.status(401).send("Token Expired!"); - } - - else { - /****** ASSIGN USER FETCHED FROM TOKEN TO REQ.USER ********/ - req.userId = decoded.userId - next(); - } - }) -} + }); +}; module.exports = { - verifyToken -} \ No newline at end of file + verifyToken, +}; diff --git a/middlewares/validateUserRequestBody.js b/middlewares/validateUserRequestBody.js index acb38d9..9a46144 100644 --- a/middlewares/validateUserRequestBody.js +++ b/middlewares/validateUserRequestBody.js @@ -7,7 +7,6 @@ const { formatDate } = require("../utils/formatDate"); /*** VALIDATE DETAILS PROVIDED BY USER ******/ let validateUserRequestBody = async (req, res, next) => { try { - let { name, userId, email, password } = req.body; /************** NAME VALIDATION ****************** */ if (!name) { @@ -35,7 +34,9 @@ let validateUserRequestBody = async (req, res, next) => { updatedAt: IndiaDateUpdatedAt, }; console.log("\nuserId with this data already exists!", userData); - return res.status(400).send(`Failed!, userId '${userData.userId}' already exists!`); + return res + .status(400) + .send(`Failed!, userId '${userData.userId}' already exists!`); } /************** EMAIL VALIDATION ****************** */ @@ -49,8 +50,10 @@ let validateUserRequestBody = async (req, res, next) => { const emailReq = await User.findOne({ email: email }); if (emailReq != null) { - console.log("\nEmail \'"+email+ "\' already exists"); - return res.status(400).send(`Failed! Email '${emailReq.email}' already exists`); + console.log("\nEmail '" + email + "' already exists"); + return res + .status(400) + .send(`Failed! Email '${emailReq.email}' already exists`); } /************* PASSWORD VALIDATION ***************** */ @@ -60,7 +63,6 @@ let validateUserRequestBody = async (req, res, next) => { } next(); - } catch (error) { console.log("Error at validateUserRequestBody:", error.message); res.status(400).send("Internal Error Occured!"); diff --git a/models/post.model.js b/models/post.model.js index 935d351..8c7bbc0 100644 --- a/models/post.model.js +++ b/models/post.model.js @@ -1,34 +1,33 @@ - //import mongoose const mongoose = require("mongoose"); -const User = require('./user.model') +const User = require("./user.model"); //route handler const postSchema = new mongoose.Schema({ - title: { - type: String, - required: [true,`NOT PROVIDED 😔`], - unique:true - }, - content: { - type: String, - required: [true,`NOT PROVIDED ☚ī¸`] - }, - user: { - type: mongoose.Schema.Types.ObjectId, - required:true, - ref: "User" - }, - createdAt: { - type: Date, - immutable:true, - default: Date.now() - }, - updatedAt: { - type: Date, - default: Date.now() - } -}) + title: { + type: String, + required: [true, `NOT PROVIDED 😔`], + unique: true, + }, + content: { + type: String, + required: [true, `NOT PROVIDED ☚ī¸`], + }, + user: { + type: mongoose.Schema.Types.ObjectId, + required: true, + ref: "User", + }, + createdAt: { + type: Date, + immutable: true, + default: Date.now(), + }, + updatedAt: { + type: Date, + default: Date.now(), + }, +}); //export -module.exports = mongoose.model("Post", postSchema); \ No newline at end of file +module.exports = mongoose.model("Post", postSchema); diff --git a/models/user.model.js b/models/user.model.js index c045280..513b175 100644 --- a/models/user.model.js +++ b/models/user.model.js @@ -1,39 +1,38 @@ - //import mongoose const mongoose = require("mongoose"); //route handler const userSchema = new mongoose.Schema({ - name:{ - type:String, - required:[true,`NOT PROVIDED 😒`] - }, - userId:{ - type:String, - required:[true,`NOT PROVIDED 😕`], - unique:true, - lowercase:true - }, - password:{ - type:String, - required:[true,`NOT PROVIDED 🙁`] - }, - email:{ - type:String, - required:[true,`NOT PROVIDED 😠`], - lowercase:true, - unique:true - }, - createdAt: { - type: Date, - immutable:true, - default: Date.now() - }, - updatedAt: { - type: Date, - default: Date.now() - } -}) + name: { + type: String, + required: [true, `NOT PROVIDED 😒`], + }, + userId: { + type: String, + required: [true, `NOT PROVIDED 😕`], + unique: true, + lowercase: true, + }, + password: { + type: String, + required: [true, `NOT PROVIDED 🙁`], + }, + email: { + type: String, + required: [true, `NOT PROVIDED 😠`], + lowercase: true, + unique: true, + }, + createdAt: { + type: Date, + immutable: true, + default: Date.now(), + }, + updatedAt: { + type: Date, + default: Date.now(), + }, +}); //export -module.exports = mongoose.model("User", userSchema); \ No newline at end of file +module.exports = mongoose.model("User", userSchema); diff --git a/package-lock.json b/package-lock.json index e00dbef..9b40e2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "jsonwebtoken": "^9.0.2", "moment-timezone": "^0.5.44", @@ -24,7 +25,8 @@ "user-ip": "^1.1.0" }, "devDependencies": { - "nodemon": "^3.0.2" + "nodemon": "^3.0.2", + "prettier": "^3.1.1" }, "engines": { "node": ">=18.19.0" @@ -589,6 +591,20 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.5.tgz", + "integrity": "sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -1647,6 +1663,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/package.json b/package.json index fdce14c..2784250 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "scripts": { "build": "npm install bcrypt body-parser dotenv express jsonwebtoken mongoose node-rest-client moment-timezone user-ip axios node-fetch --save", "start": "node ./index.js", - "dev": "nodemon ./index.js" + "dev": "nodemon ./index.js", + "prettier": "prettier --write \"./**/*.{js,jsx,ts,tsx,json,css,scss,md}\"", + "prettier-check": "prettier --check \"./**/*.{js,jsx,ts,tsx,json,css,scss,md}\"" }, "author": "Nilanjan", "license": "ISC", @@ -18,6 +20,7 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "jsonwebtoken": "^9.0.2", "moment-timezone": "^0.5.44", @@ -27,7 +30,8 @@ "user-ip": "^1.1.0" }, "devDependencies": { - "nodemon": "^3.0.2" + "nodemon": "^3.0.2", + "prettier": "^3.1.1" }, "keywords": [ "mongodb", diff --git a/routes/auth.routes.js b/routes/auth.routes.js index 9020113..ae4d3ca 100644 --- a/routes/auth.routes.js +++ b/routes/auth.routes.js @@ -1,15 +1,17 @@ -const {signup, signin, changePassword, deleteUser} = require("../controllers/auth.controller"); +const { + signup, + signin, + changePassword, + deleteUser, +} = require("../controllers/auth.controller"); const { verifyToken } = require("../middlewares/AuthJWT"); -const {validateUserRequestBody} = require("../middlewares/validateUserRequestBody"); +const { + validateUserRequestBody, +} = require("../middlewares/validateUserRequestBody"); -module.exports = function(app){ - app.post("/api/v1/auth/signup", - [validateUserRequestBody] , signup); - app.post("/api/v1/auth/signin", signin) - app.put("/changepassword", - verifyToken, - changePassword) - app.delete("/deleteuser", - verifyToken, - deleteUser) -} \ No newline at end of file +module.exports = function (app) { + app.post("/api/v1/auth/signup", [validateUserRequestBody], signup); + app.post("/api/v1/auth/signin", signin); + app.put("/changepassword", verifyToken, changePassword); + app.delete("/deleteuser", verifyToken, deleteUser); +}; diff --git a/routes/post.routes.js b/routes/post.routes.js index 94329fb..ab2623c 100644 --- a/routes/post.routes.js +++ b/routes/post.routes.js @@ -1,13 +1,19 @@ -'use strict'; -const { addPost, getPostByUserId, deletePostByPostTitle, deleteAllPostsByUserId, getData} = require('../controllers/post.controllers') -const { verifyToken } = require('../middlewares/AuthJWT') +"use strict"; +const { + addPost, + getPostByUserId, + deletePostByPostTitle, + deleteAllPostsByUserId, + getData, +} = require("../controllers/post.controllers"); +const { verifyToken } = require("../middlewares/AuthJWT"); module.exports = function (app) { - app.post("/api/v1/addPost", verifyToken, addPost) - app.get("/api/v1/posts", verifyToken, getPostByUserId) - app.delete("/api/v1/posts", verifyToken, deletePostByPostTitle) - app.delete("/api/v1/allposts", verifyToken, deleteAllPostsByUserId) + app.post("/api/v1/addPost", verifyToken, addPost); + app.get("/api/v1/posts", verifyToken, getPostByUserId); + app.delete("/api/v1/posts", verifyToken, deletePostByPostTitle); + app.delete("/api/v1/allposts", verifyToken, deleteAllPostsByUserId); - /*** TEST DATA USING AXIOS API **** */ - // app.get("/api/getdata", getData) -} \ No newline at end of file + /*** TEST DATA USING AXIOS API **** */ + // app.get("/api/getdata", getData) +}; diff --git a/utils/api-rate-limiter.js b/utils/api-rate-limiter.js new file mode 100644 index 0000000..934084e --- /dev/null +++ b/utils/api-rate-limiter.js @@ -0,0 +1,25 @@ +require("dotenv").config({ + path: `./.env`, +}); +const { rateLimit } = require("express-rate-limit"); + +const limiter = rateLimit({ + windowMs: process.env.RATE_LIMIT_TIME * 60 * 1000 || 15 * 60 * 1000, // default is 15 minutes + max: process.env.MAX_REQUESTS || 10, // Limit each IP to 5 requests per `window` (here, per 15 minutes) + skipFailedRequests: true, // If any request not failed that will not count + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + keyGenerator: (req) => `${req.protocol}://${req.hostname}${req.originalUrl}`, + message: async (req, res) => { + console.log( + `\n${req.protocol}://${req.hostname}${req.originalUrl} [${req.method}] -> API is Rate-limited` + ); + return res.status(429).json({ + message: "Too many requests, please try again later.", + }); + }, +}); + +module.exports = { + limiter +};