From 582daa2c3b97dc2945a8c5dcd673c39ea7ee278e Mon Sep 17 00:00:00 2001 From: Manik Maity Date: Fri, 8 Nov 2024 12:51:14 +0530 Subject: [PATCH] Added signin route in backend --- backend/package-lock.json | 130 ++++++++++++++++++++ backend/package.json | 2 + backend/src/config/veriables.js | 3 +- backend/src/controllers/user.controller.js | 75 +++++++---- backend/src/index.js | 2 + backend/src/repositories/user.repo.js | 10 ++ backend/src/routes/v1/user.route.js | 5 +- backend/src/services/user.service.js | 37 +++++- backend/src/validations/signinValidation.js | 20 +++ 9 files changed, 256 insertions(+), 28 deletions(-) create mode 100644 backend/src/validations/signinValidation.js diff --git a/backend/package-lock.json b/backend/package-lock.json index 8bbb939..164c2a0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,9 +10,11 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.0", "socket.io": "^4.8.1", "swagger-jsdoc": "^6.2.8", @@ -324,6 +326,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", @@ -427,6 +435,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -530,6 +560,15 @@ "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==", + "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", @@ -1013,6 +1052,55 @@ "js-yaml": "bin/js-yaml.js" } }, + "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/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==", + "license": "MIT" + }, + "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==", + "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", @@ -1028,18 +1116,60 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "license": "MIT" }, + "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.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "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.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "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/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", diff --git a/backend/package.json b/backend/package.json index 702ec68..2aefa21 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,9 +13,11 @@ "description": "", "dependencies": { "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.0", "socket.io": "^4.8.1", "swagger-jsdoc": "^6.2.8", diff --git a/backend/src/config/veriables.js b/backend/src/config/veriables.js index e99a116..64a49c7 100644 --- a/backend/src/config/veriables.js +++ b/backend/src/config/veriables.js @@ -3,4 +3,5 @@ dotenv.config(); export const PORT = Number(process.env.PORT); export const DB_URL = process.env.DB_CONNECTION; -export const SALT = Number(process.env.SALT_ROUNDS); \ No newline at end of file +export const SALT = Number(process.env.SALT_ROUNDS); +export const JWT_PRIVATE = process.env.JWT_PRIVATE; \ No newline at end of file diff --git a/backend/src/controllers/user.controller.js b/backend/src/controllers/user.controller.js index 805e625..e444a50 100644 --- a/backend/src/controllers/user.controller.js +++ b/backend/src/controllers/user.controller.js @@ -1,28 +1,55 @@ -import { signupService } from "../services/user.service.js"; +import { signinService, signupService } from "../services/user.service.js"; export async function signupController(req, res) { - try { - const { username, email, password } = req.body; - const user = await signupService(username, email, password); - res.status(201).json({ - success : true, - message : "User created successfully", - data : user - }) + try { + const { username, email, password } = req.body; + const user = await signupService(username, email, password); + res.status(201).json({ + success: true, + message: "User created successfully", + data: user, + }); + } catch (err) { + console.log(err); + if (err.statusCode) { + res.status(err.statusCode).json({ + success: false, + message: err.message, + }); + } else { + res.status(500).json({ + success: false, + message: err.message, + }); } - catch(err){ - console.log(err); - if (err.statusCode){ - res.status(err.statusCode).json({ - success : false, - message : err.message - }) - } - else { - res.status(500).json({ - success : false, - message : err.message - }) - } + } +} + +export async function signinController(req, res) { + try { + const { email, password } = req.body; + const { token, userData } = await signinService(email, password); + res.cookie("access-token", token, { + httpOnly: true, + maxAge: 10 * 24 * 60 * 60 * 1000, // 10 days + }).status(200).json({ + success : true, + message : "User signedin successfully.", + user : userData + }); + + } catch (err) { + console.log(err); + if (err.statusCode) { + res.status(err.statusCode).json({ + success: false, + message: err.message, + }); + } else { + res.status(500).json({ + success: false, + message: err.message, + }); } -} \ No newline at end of file + } +} diff --git a/backend/src/index.js b/backend/src/index.js index b47cc4f..8cf5b72 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -7,6 +7,7 @@ import { connectDB } from './config/dbConfig.js'; import userRouter from './routes/v1/user.route.js'; import swaggerDocs from '../swagger.js'; import swaggerUi from 'swagger-ui-express'; +import cookieParser from "cookie-parser"; const app = express(); @@ -20,6 +21,7 @@ const io = new Server(httpServer, { } }) +app.use(cookieParser()); app.use(cors()) app.use(express.json()) app.get("/ping", (_req, res) => { diff --git a/backend/src/repositories/user.repo.js b/backend/src/repositories/user.repo.js index 2ba4364..aa3136f 100644 --- a/backend/src/repositories/user.repo.js +++ b/backend/src/repositories/user.repo.js @@ -13,4 +13,14 @@ export const createUser = async (username, email, password) => { catch(err) { throw err } +} + +export async function findUserByEmail(email) { + try{ + const user = await UserModel.findOne({email}); + return user; + } + catch(err){ + throw err; + } } \ No newline at end of file diff --git a/backend/src/routes/v1/user.route.js b/backend/src/routes/v1/user.route.js index 4fcb15b..2340295 100644 --- a/backend/src/routes/v1/user.route.js +++ b/backend/src/routes/v1/user.route.js @@ -1,7 +1,8 @@ import express from "express"; -import { signupController } from "../../controllers/user.controller.js"; +import { signinController, signupController } from "../../controllers/user.controller.js"; import validate from "../../validations/validator.js"; import signupSchema from "../../validations/signupValidation.js"; +import signinSchema from "../../validations/signinValidation.js"; const userRouter = express.Router(); /** @@ -39,4 +40,6 @@ userRouter.get("/test", (req, res) => { */ userRouter.post("/signup", validate(signupSchema), signupController); +userRouter.post("/signin", validate(signinSchema), signinController); + export default userRouter; \ No newline at end of file diff --git a/backend/src/services/user.service.js b/backend/src/services/user.service.js index 24202a7..410b48a 100644 --- a/backend/src/services/user.service.js +++ b/backend/src/services/user.service.js @@ -1,6 +1,7 @@ -import { SALT } from "../config/veriables.js"; -import { createUser } from "../repositories/user.repo.js"; +import { JWT_PRIVATE, SALT } from "../config/veriables.js"; +import { createUser, findUserByEmail } from "../repositories/user.repo.js"; import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; export async function signupService(username, email, password) { try { @@ -24,3 +25,35 @@ export async function signupService(username, email, password) { } } } + +export async function signinService(email, password) { + try{ + if (email.trim() == "" || password.trim() == "") { + throw { + statusCode: 400, + message: "All fields are required", + }; + } + + const user = await findUserByEmail(email); + if (!user){ + throw { + statusCode : 404, + message : "User not found." + } + } + if (!bcrypt.compareSync(password, user?.password)){ + throw { + statusCode : 401, + message : "Password isn't correct." + } + } + + const token = jwt.sign({id : user._id}, JWT_PRIVATE); + const {password:pass, ...userData} = user._doc; + return {token, userData}; + } + catch(err){ + throw err; + } +} \ No newline at end of file diff --git a/backend/src/validations/signinValidation.js b/backend/src/validations/signinValidation.js new file mode 100644 index 0000000..92129a8 --- /dev/null +++ b/backend/src/validations/signinValidation.js @@ -0,0 +1,20 @@ +import { z } from "zod"; + +const signinSchema = z.object({ + email: z + .string({ + required_error: "Email is required.", + }) + .min(1, "Email is required.") + .max(200, "Email cant be more then 200 charecters.") + .email("Invalid email input"), + + password: z + .string({ + required_error: "Password is required", + }) + .min(6, "Password cant be less than 6 charecters") + .max(50, "Password cant be more than 50 charecters"), +}); + +export default signinSchema;