From 4747cae6d1b7cef39bfa926c45d36702a556ce1b Mon Sep 17 00:00:00 2001 From: Haris Shah Date: Sun, 3 Mar 2024 01:35:24 +0500 Subject: [PATCH] feat: get user based on their token --- apps/api/package.json | 2 + apps/api/src/controllers/user_controller.ts | 24 +++++++++++- apps/api/src/middleware/auth_middleware.ts | 41 +++++++++++++++++++++ apps/api/src/routes/user_route.ts | 21 ++++++++--- apps/api/src/server.ts | 2 + apps/api/src/types/express/index.d.ts | 24 ++++++++++++ package-lock.json | 41 ++++++++++++++++++--- 7 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 apps/api/src/middleware/auth_middleware.ts create mode 100644 apps/api/src/types/express/index.d.ts diff --git a/apps/api/package.json b/apps/api/package.json index 41a5395..3811cbe 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -18,6 +18,7 @@ "@repo/logger": "*", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", @@ -31,6 +32,7 @@ "@repo/typescript-config": "*", "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", + "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", diff --git a/apps/api/src/controllers/user_controller.ts b/apps/api/src/controllers/user_controller.ts index 8dba93b..3d908fc 100644 --- a/apps/api/src/controllers/user_controller.ts +++ b/apps/api/src/controllers/user_controller.ts @@ -4,10 +4,13 @@ import { isValidObjectId } from 'mongoose' import { log } from 'console' import { generate_token } from 'utils/generate_token' -//* @desc Post user +//* @desc Register user //* route POST /api/user //? @access Public -export async function create_user(req: Request, res: Response): Promise { +export async function register_user( + req: Request, + res: Response +): Promise { try { const user_data = req.body // check if user email already exists @@ -98,3 +101,20 @@ export async function update_user(req: Request, res: Response): Promise { res.status(500).json({ error: 'Internal server error' }) } } + +//* @desc Get User +//* route GET /api/user/profile +//! @access Private +export async function get_user(req: Request, res: Response): Promise { + try { + const user = { + name: req.user?.name, + email: req.user?.email, + _id: req.user?._id, + } + res.status(200).json(user) + } catch (error) { + log('Error fetching user:', error) + res.status(500).json({ error: 'Internal server error' }) + } +} diff --git a/apps/api/src/middleware/auth_middleware.ts b/apps/api/src/middleware/auth_middleware.ts new file mode 100644 index 0000000..9d8c9c3 --- /dev/null +++ b/apps/api/src/middleware/auth_middleware.ts @@ -0,0 +1,41 @@ +import { Request as ExpressRequest, Response, NextFunction } from 'express' +import jwt, { JwtPayload } from 'jsonwebtoken' +import { USER_SCHEMA } from '@/models/user_model' +import { log } from 'console' + +interface User { + name?: string + email: string + password: string +} + +interface Request extends ExpressRequest { + user?: User +} + +export async function protect_route( + req: Request, + res: Response, + next: NextFunction +) { + try { + const token = req.cookies.token + if (!token) { + res.status(401).json({ error: 'Unauthorized' }) + return + } + const secret = process.env.JWT_SECRET + if (!secret) { + res.status(500).json({ error: 'JWT secret is undefined' }) + return + } + const decoded = jwt.verify(token, secret) as JwtPayload + req.user = (await USER_SCHEMA.findById(decoded.user_id).select( + '-password' + )) as User + + next() + } catch (error) { + res.status(500).json({ error: 'Internal server error' }) + } +} diff --git a/apps/api/src/routes/user_route.ts b/apps/api/src/routes/user_route.ts index 1a7b8d5..130ad73 100644 --- a/apps/api/src/routes/user_route.ts +++ b/apps/api/src/routes/user_route.ts @@ -5,24 +5,35 @@ import { USER_VALIDATION_SCHEMA } from '@/validations/user_validation' import { login_user, logout_user, - create_user, + register_user, update_user, + get_user, } from '@/controllers/user_controller' +import { protect_route } from '@/middleware/auth_middleware' -//* @desc Post user +//* @desc Create user //? @access Public -router.post('/', validate_schema(USER_VALIDATION_SCHEMA), create_user) +router.post('/', validate_schema(USER_VALIDATION_SCHEMA), register_user) //* @desc Login user //? @access Public -router.get('/', validate_schema(USER_VALIDATION_SCHEMA), login_user) +router.get('/login', validate_schema(USER_VALIDATION_SCHEMA), login_user) //* @desc Logout user //? @access Public router.post('/logout', logout_user) +//* @desc Get User +//! @access Private +router.get('/profile', protect_route, get_user) + //* @desc Update user //! @access Private -router.patch('/:id', validate_schema(USER_VALIDATION_SCHEMA), update_user) +router.patch( + '/:id', + protect_route, + validate_schema(USER_VALIDATION_SCHEMA), + update_user +) export default router diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 8ab8cff..d9bdb22 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -3,6 +3,7 @@ import express, { type Express } from 'express' import morgan from 'morgan' import cors from 'cors' import { config } from 'dotenv' +import cookieParser from 'cookie-parser' config() @@ -14,6 +15,7 @@ export const createServer = (): Express => { .use(urlencoded({ extended: true })) .use(json()) .use(cors()) + .use(cookieParser()) .get('/health', (_, res) => { return res.json({ ok: true }) }) diff --git a/apps/api/src/types/express/index.d.ts b/apps/api/src/types/express/index.d.ts new file mode 100644 index 0000000..0d78638 --- /dev/null +++ b/apps/api/src/types/express/index.d.ts @@ -0,0 +1,24 @@ +export {} + +declare global { + namespace Express { + export interface Request { + job?: { + id: string + title: string + description: string + company: string + location: string + salary: number + created_at: string + updated_at: string + } + user?: { + _id?: string + name?: string + email: string + password: string + } + } + } +} diff --git a/package-lock.json b/package-lock.json index d239ad6..16317de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@repo/logger": "*", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", @@ -54,6 +55,7 @@ "@repo/typescript-config": "*", "@types/bcrypt": "^5.0.2", "@types/body-parser": "^1.19.5", + "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", @@ -2575,6 +2577,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -4298,6 +4309,31 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -6345,11 +6381,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",