From e1a60bdccdb17211831a3cacb8313bdabee72f33 Mon Sep 17 00:00:00 2001 From: jaanvi Date: Tue, 3 Jun 2025 11:51:54 +0530 Subject: [PATCH] JWT-bcrypt-learning --- app.js | 27 ++----- config/db.js | 6 +- controllers/authController.js | 58 +++++++++++++++ controllers/userController.js | 1 + middleware/authMiddleware.js | 24 ++++++ middleware/logger.js | 6 ++ models/User.js | 56 +++++++++----- nodemon.json | 4 + package-lock.json | 136 ++++++++++++++++++++++++++++++++++ package.json | 4 + routes/authRoutes.js | 21 ++++++ 11 files changed, 306 insertions(+), 37 deletions(-) create mode 100644 controllers/authController.js create mode 100644 middleware/authMiddleware.js create mode 100644 middleware/logger.js create mode 100644 nodemon.json create mode 100644 routes/authRoutes.js diff --git a/app.js b/app.js index 540e671..30c5c28 100644 --- a/app.js +++ b/app.js @@ -2,34 +2,23 @@ require("dotenv").config(); const express = require("express"); const connectDB= require("./config/db"); const userRoutes= require("./routes/userRoutes"); +const authRoutes = require("./routes/authRoutes"); +const logger = require('./middleware/logger') const app= express(); const PORT = process.env.PORT || 5000; //DB connection:Done connectDB(); //use in built-middleware to parse the data form body -app.use(express.urlencoded({ extended: false })) +//parse the json data +app.use(express.json()); +//parse the url-encoded data +app.use(express.urlencoded({ extended: false })); +app.use(logger); //Routes app.use("/api/users",userRoutes); - +app.use("/api/user", authRoutes); app.listen(PORT,()=>{ console.log(`Server is running on port ${PORT}`); }) - - -// const logger = require("./middleware/logger"); -// const userRoutes = require("./routes/userRoutes"); - -// const app = express(); -// const PORT = process.env.PORT || 5000; - -// connectDB(); - -// app.use(express.json()); -// app.use(logger); // custom middleware -// app.use("/api/users", userRoutes); - -// app.listen(PORT, () => { -// console.log(`Server is running on port ${PORT}`); -// }); diff --git a/config/db.js b/config/db.js index 4476cff..82e8ae8 100644 --- a/config/db.js +++ b/config/db.js @@ -1,4 +1,5 @@ const mongoose = require("mongoose"); + const connectDB = async()=>{ try{ await mongoose.connect(process.env.MONGO_URI); @@ -9,4 +10,7 @@ const connectDB = async()=>{ } } -module.exports = connectDB; \ No newline at end of file +module.exports = connectDB; + + + diff --git a/controllers/authController.js b/controllers/authController.js new file mode 100644 index 0000000..69e0531 --- /dev/null +++ b/controllers/authController.js @@ -0,0 +1,58 @@ +const User = require("../models/User"); +const jwt = require("jsonwebtoken"); + +const generateToken = (id) => { + return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "1h" }); +}; + +//Register +const registerUser = async (req, res) => { + const { username, email, password } = req.body; + + try { + const userExists = await User.findOne({ email }); + if (userExists) + return res.status(400).json({ message: "User already exists" }); + + const user = await User.create({ username, email, password }); + + res.status(201).json({ + _id: user._id, + username: user.username, + email: user.email, + token: generateToken(user._id), + }); + } catch (error) { + console.error("Error in registerUser:", error.message); + res.status(500).json({ message: "Server error" }); +} + +}; +//Login +const loginUser = async (req, res) => { + const { email, password } = req.body; + + try { + const user = await User.findOne({ email }); + if (!user) return res.status(400).json({ message: "Invalid credentials" }); + + const isMatch = await user.matchPassword(password); + if (!isMatch) + return res.status(400).json({ message: "Invalid credentials" }); + + res.status(200).json({ + _id: user._id, + username: user.username, + email: user.email, + token: generateToken(user._id), + message: "success", + }); + } catch (error) { + res.status(500).json({ message: "Server error" }); + } +}; +const getProfile = async (req, res) => { + res.json(req.user); +}; + +module.exports = { registerUser, loginUser, getProfile }; diff --git a/controllers/userController.js b/controllers/userController.js index 94ff8da..15be1d7 100644 --- a/controllers/userController.js +++ b/controllers/userController.js @@ -52,6 +52,7 @@ const deleteUser= async(req,res)=>{ if(!user){ return res.status(404).json({message: "User not found"}); } + res.status(200).json({message:"success"}) } catch (err) { res.status(500).json({ error: err.message }); } diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js new file mode 100644 index 0000000..5bcf064 --- /dev/null +++ b/middleware/authMiddleware.js @@ -0,0 +1,24 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/User"); + +const authToken = async (req, res, next) => { + let token; + if ( + req.headers.authorization && + req.headers.authorization.startsWith("Bearer") + ) { + try { + token = req.headers.authorization.split(" ")[1]; // Correct split by space + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = await User.findById(decoded.id).select("-password"); + next(); + } catch (error) { + console.error(error); + res.status(401).json({ message: "Not authorized, token failed" }); + } + } else { + res.status(401).json({ message: "Not authorized, no token" }); + } +}; + +module.exports = authToken; diff --git a/middleware/logger.js b/middleware/logger.js new file mode 100644 index 0000000..fa377db --- /dev/null +++ b/middleware/logger.js @@ -0,0 +1,6 @@ +// logger.js +function logger(req, res, next) { + console.log(`${req.method} ${req.url} at ${new Date().toISOString()}`); + next(); +} +module.exports = logger; diff --git a/models/User.js b/models/User.js index 5e74ed8..6ac872f 100644 --- a/models/User.js +++ b/models/User.js @@ -1,19 +1,41 @@ const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); const userSchema = new mongoose.Schema({ - first_name:{ - type:String, - required:true - }, - last_name:{ - type:String, - }, - email:{ - type:String, - required:true, - unique:true - }, - age:{ - type:Number - } -}) -module.exports = mongoose.model("User",userSchema); + first_name: { + type: String, + required: false, + }, + last_name: { + type: String, + }, + email: { + type: String, + required: true, + unique: true, + }, + age: { + type: Number, + }, + username: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, +}); + +userSchema.pre("save", async function (next) { + if (!this.isModified("password")) return next(); + + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); + next(); +}); + +userSchema.methods.matchPassword = async function (enteredPassword) { + return await bcrypt.compare(enteredPassword, this.password); +}; +module.exports = mongoose.model("User", userSchema); diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..76d617b --- /dev/null +++ b/nodemon.json @@ -0,0 +1,4 @@ +{ + "watch": ["."], + "legacyWatch": true +} diff --git a/package-lock.json b/package-lock.json index cb6f92b..b83a122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "mongodb": "^6.16.0", "mongoose": "^8.15.1", "nodemon": "^3.1.10" @@ -72,6 +74,20 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -135,6 +151,12 @@ "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==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -293,6 +315,15 @@ "node": ">= 0.4" } }, + "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==", + "license": "Apache-2.0", + "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", @@ -655,6 +686,49 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "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/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "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==", + "license": "MIT", + "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", @@ -664,6 +738,48 @@ "node": ">=12.0.0" } }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -845,6 +961,26 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", diff --git a/package.json b/package.json index b2dec8b..29b2660 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,14 @@ "license": "ISC", "description": "", "dependencies": { + "bcrypt": "^6.0.0", "dotenv": "^16.5.0", "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", "mongodb": "^6.16.0", "mongoose": "^8.15.1", "nodemon": "^3.1.10" } + + } diff --git a/routes/authRoutes.js b/routes/authRoutes.js new file mode 100644 index 0000000..745b564 --- /dev/null +++ b/routes/authRoutes.js @@ -0,0 +1,21 @@ +const express = require("express"); +const { + registerUser, + loginUser, + getProfile, +} = require("../controllers/authController"); +const authToken = require("../middleware/authMiddleware"); + +const router = express.Router(); + +// Register user +router.post("/register",registerUser ); + +// Login user +router.post("/login", loginUser); + +//protectRoute +router.get("/profile", authToken, getProfile); + + +module.exports = router;