From 256ca4538afe5dc61f3607f14940fdad58b8c257 Mon Sep 17 00:00:00 2001 From: Saritha R Date: Sat, 17 Jan 2026 16:41:09 -0500 Subject: [PATCH] feat: Add JWT authentication and role-based authorization Implement JWT-based authentication with role-based access control: - Add auth middleware for token verification and role authorization - Create /auth/login and /auth/register endpoints - Protect all routes except /health and /auth - Add role-based permissions (admin/user) to routes - Add jsonwebtoken and bcryptjs dependencies BREAKING CHANGE: API endpoints now require Bearer token authentication --- api/middleware/auth.js | 77 ++++++++++++++ api/routes/auth.js | 162 +++++++++++++++++++++++++++++ api/routes/orders.js | 20 ++-- api/routes/products.js | 26 +++-- api/routes/visitors.js | 18 ++-- app.js | 15 ++- package-lock.json | 230 +++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 8 files changed, 525 insertions(+), 25 deletions(-) create mode 100644 api/middleware/auth.js create mode 100644 api/routes/auth.js diff --git a/api/middleware/auth.js b/api/middleware/auth.js new file mode 100644 index 0000000..1c51879 --- /dev/null +++ b/api/middleware/auth.js @@ -0,0 +1,77 @@ +const jwt = require('jsonwebtoken'); + +// Secret key - in production, use environment variable +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; + +// Authentication middleware - verifies JWT token +const authenticate = (req, res, next) => { + try { + // Get token from Authorization header + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + error: { + message: 'No token provided. Please include a Bearer token in Authorization header.' + } + }); + } + + // Extract token (remove 'Bearer ' prefix) + const token = authHeader.substring(7); + + // Verify token + const decoded = jwt.verify(token, JWT_SECRET); + + // Attach user info to request object + req.user = decoded; + next(); + } catch (error) { + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ + error: { + message: 'Invalid token' + } + }); + } + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ + error: { + message: 'Token expired' + } + }); + } + return res.status(500).json({ + error: { + message: 'Authentication error' + } + }); + } +}; + +// Authorization middleware - checks user roles +const authorize = (...allowedRoles) => { + return (req, res, next) => { + if (!req.user) { + return res.status(401).json({ + error: { + message: 'Authentication required' + } + }); + } + + const userRole = req.user.role; + + if (!allowedRoles.includes(userRole)) { + return res.status(403).json({ + error: { + message: 'Access denied. Insufficient permissions.' + } + }); + } + + next(); + }; +}; + +module.exports = { authenticate, authorize, JWT_SECRET }; diff --git a/api/routes/auth.js b/api/routes/auth.js new file mode 100644 index 0000000..3a9f772 --- /dev/null +++ b/api/routes/auth.js @@ -0,0 +1,162 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcryptjs'); +const { JWT_SECRET } = require('../middleware/auth'); + +// In-memory user store (use database in production) +// Pre-hashed passwords: admin123 and user123 +const users = [ + { + id: 1, + username: 'admin', + password: '$2a$10$rOzJqZqZqZqZqZqZqZqZqO', // Will be initialized with actual hash + role: 'admin' + }, + { + id: 2, + username: 'user', + password: '$2a$10$rOzJqZqZqZqZqZqZqZqZqO', // Will be initialized with actual hash + role: 'user' + } +]; + +// Initialize with hashed passwords (run once on server start) +async function initializeUsers() { + for (let user of users) { + if (user.username === 'admin' && user.password.startsWith('$2a$10$rOzJqZqZqZqZqZqZqZqZqO')) { + user.password = await bcrypt.hash('admin123', 10); + } else if (user.username === 'user' && user.password.startsWith('$2a$10$rOzJqZqZqZqZqZqZqZqZqO')) { + user.password = await bcrypt.hash('user123', 10); + } + } +} +initializeUsers(); + +// Login endpoint +router.post('/login', async (req, res) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ + error: { + message: 'Username and password are required' + } + }); + } + + // Find user + const user = users.find(u => u.username === username); + if (!user) { + return res.status(401).json({ + error: { + message: 'Invalid credentials' + } + }); + } + + // Verify password + const isValidPassword = await bcrypt.compare(password, user.password); + if (!isValidPassword) { + return res.status(401).json({ + error: { + message: 'Invalid credentials' + } + }); + } + + // Generate JWT token + const token = jwt.sign( + { + id: user.id, + username: user.username, + role: user.role + }, + JWT_SECRET, + { expiresIn: '24h' } + ); + + res.status(200).json({ + message: 'Login successful', + token: token, + user: { + id: user.id, + username: user.username, + role: user.role + } + }); + } catch (error) { + res.status(500).json({ + error: { + message: 'Login failed' + } + }); + } +}); + +// Register endpoint +router.post('/register', async (req, res) => { + try { + const { username, password, role = 'user' } = req.body; + + if (!username || !password) { + return res.status(400).json({ + error: { + message: 'Username and password are required' + } + }); + } + + // Check if user exists + if (users.find(u => u.username === username)) { + return res.status(409).json({ + error: { + message: 'Username already exists' + } + }); + } + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Create new user + const newUser = { + id: users.length + 1, + username, + password: hashedPassword, + role: role || 'user' + }; + + users.push(newUser); + + // Generate token + const token = jwt.sign( + { + id: newUser.id, + username: newUser.username, + role: newUser.role + }, + JWT_SECRET, + { expiresIn: '24h' } + ); + + res.status(201).json({ + message: 'User registered successfully', + token: token, + user: { + id: newUser.id, + username: newUser.username, + role: newUser.role + } + }); + } catch (error) { + res.status(500).json({ + error: { + message: 'Registration failed' + } + }); + } +}); + +module.exports = router; diff --git a/api/routes/orders.js b/api/routes/orders.js index da8d457..6de6bb0 100644 --- a/api/routes/orders.js +++ b/api/routes/orders.js @@ -1,16 +1,23 @@ const express = require('express'); const router = express.Router(); +const { authenticate, authorize } = require('../middleware/auth'); -router.get('/', (req, res) => { +// All routes require authentication +router.use(authenticate); + +// GET all orders - requires 'user' or 'admin' role +router.get('/', authorize('user', 'admin'), (req, res) => { res.status(200).json({ - message: 'Order was fetched' + message: 'Order was fetched', + user: req.user.username }); }); -router.post('/', (req, res) => { +// POST order - requires 'user' or 'admin' role +router.post('/', authorize('user', 'admin'), (req, res) => { res.status(201).json({ @@ -19,8 +26,8 @@ router.post('/', (req, res) => { }); - -router.get('/:orderID', (req, res) => { +// GET order by ID - requires 'user' or 'admin' role +router.get('/:orderID', authorize('user', 'admin'), (req, res) => { res.status(200).json({ message: 'order details', @@ -29,7 +36,8 @@ router.get('/:orderID', (req, res) => { }); -router.delete('/:orderID', (req, res) => { +// DELETE order - requires 'admin' role only +router.delete('/:orderID', authorize('admin'), (req, res) => { res.status(200).json({ message: 'order deleted', diff --git a/api/routes/products.js b/api/routes/products.js index 3a0887e..92ca199 100644 --- a/api/routes/products.js +++ b/api/routes/products.js @@ -1,16 +1,23 @@ const express = require('express'); const router = express.Router(); +const { authenticate, authorize } = require('../middleware/auth'); -router.get('/', (req, res) => { +// All routes require authentication +router.use(authenticate); + +// GET all products - requires 'user' or 'admin' role +router.get('/', authorize('user', 'admin'), (req, res) => { res.status(200).json({ - message: 'Products(GET method)' + message: 'Products(GET method)', + user: req.user.username }); }); -router.post('/', (req, res) => { +// POST product - requires 'admin' role only +router.post('/', authorize('admin'), (req, res) => { res.status(201).json({ @@ -18,9 +25,8 @@ router.post('/', (req, res) => { }); }); - - -router.get('/:productID', (req, res) => { +// GET product by ID - requires 'user' or 'admin' role +router.get('/:productID', authorize('user', 'admin'), (req, res) => { const id = req.params.productID; if (id == 123) { @@ -40,16 +46,16 @@ router.get('/:productID', (req, res) => { }); - -router.patch('/:productID', (req, res) => { +// PATCH product - requires 'admin' role only +router.patch('/:productID', authorize('admin'), (req, res) => { res.status(200).json({ message: ' Product updated successfully!' }); }); - -router.delete('/:productID', (req, res) => { +// DELETE product - requires 'admin' role only +router.delete('/:productID', authorize('admin'), (req, res) => { res.status(200).json({ message: ' Product deleted successfully.' diff --git a/api/routes/visitors.js b/api/routes/visitors.js index 21f48c2..c2c55f3 100644 --- a/api/routes/visitors.js +++ b/api/routes/visitors.js @@ -1,18 +1,24 @@ const express = require('express'); const router = express.Router(); +const { authenticate, authorize } = require('../middleware/auth'); // Store visitor logs in memory (Note: In production, you'd want to use a database) const visitorLogs = []; -router.get('/', (req, res) => { +// All routes require authentication +router.use(authenticate); + +// GET visitor logs - requires 'user' or 'admin' role +router.get('/', authorize('user', 'admin'), (req, res) => { const ip = req.ip || req.connection.remoteAddress; const timestamp = new Date().toISOString(); - + // Add new visit to logs visitorLogs.push({ ip, timestamp, - userAgent: req.get('User-Agent') + userAgent: req.get('User-Agent'), + username: req.user.username }); // Return all logs @@ -24,10 +30,10 @@ router.get('/', (req, res) => { }); }); -// Get statistics about visitors -router.get('/stats', (req, res) => { +// Get statistics about visitors - requires 'admin' role only +router.get('/stats', authorize('admin'), (req, res) => { const uniqueIPs = new Set(visitorLogs.map(log => log.ip)).size; - + res.status(200).json({ status: 'ok', totalVisits: visitorLogs.length, diff --git a/app.js b/app.js index 3787e6c..1833f09 100644 --- a/app.js +++ b/app.js @@ -2,22 +2,31 @@ const express = require('express'); const app = express(); const morgan = require('morgan'); +const bodyParser = require('body-parser'); +const authRoute = require('./api/routes/auth'); const prodRoute = require('./api/routes/products'); const prodOrder = require('./api/routes/orders'); const healthRoute = require('./api/routes/healthCheck'); const visitorsRoute = require('./api/routes/visitors'); -//rerouting requests +// Middleware app.use(morgan('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +// Public routes (no authentication required) +app.use('/auth', authRoute); +app.use('/health', healthRoute); + +// Protected routes (authentication required - applied in route files) app.use('/products', prodRoute); app.use('/orders', prodOrder); -app.use('/health', healthRoute); app.use('/visitors', visitorsRoute); //error handling app.use((req, res, next) => { - const err = new Error('This page does not exist, Navigate to /products or /orders or /visitors or /health'); + const err = new Error('This page does not exist, Navigate to /products or /orders or /visitors or /health or /auth'); err.status = 404; next(err); diff --git a/package-lock.json b/package-lock.json index ee324dc..73a4953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^3.0.3", "body-parser": "^2.2.2", "chai": "^6.2.2", "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", "mocha": "^11.7.5", "mongoose": "^9.1.2", "morgan": "^1.10.1" @@ -562,6 +564,15 @@ "node": ">= 0.8" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -639,6 +650,12 @@ "node": ">=20.19.0" } }, + "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", @@ -955,6 +972,15 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "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", @@ -1920,6 +1946,55 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz", @@ -1966,6 +2041,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -1973,6 +2084,12 @@ "dev": true, "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/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2634,6 +2751,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -3609,6 +3738,11 @@ "safe-buffer": "5.1.2" } }, + "bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==" + }, "body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -3666,6 +3800,11 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==" }, + "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==" + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3881,6 +4020,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "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==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4533,6 +4680,49 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "requires": { + "jws": "^4.0.1", + "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" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "requires": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz", @@ -4565,12 +4755,47 @@ "p-locate": "^5.0.0" } }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "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==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "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==" + }, + "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==" + }, + "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==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -4995,6 +5220,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==" + }, "send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", diff --git a/package.json b/package.json index 7ab27d5..c211d19 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,11 @@ "node": ">=18.0.0" }, "dependencies": { + "bcryptjs": "^3.0.3", "body-parser": "^2.2.2", "chai": "^6.2.2", "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", "mocha": "^11.7.5", "mongoose": "^9.1.2", "morgan": "^1.10.1"