diff --git a/API_FLOW_DOCUMENTATION.md b/API_FLOW_DOCUMENTATION.md new file mode 100644 index 0000000..4880f2f --- /dev/null +++ b/API_FLOW_DOCUMENTATION.md @@ -0,0 +1,294 @@ +# API Request Flow Documentation + +## GET `/users/:userId` Request Flow + +### Complete Request Journey + +``` +Mobile App (Android/iOS) + ↓ + GET http://10.0.2.2:3200/users/:userId + Headers: Authorization: Bearer + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ Backend Server (Port 3200) │ +│ │ +│ 1. Express App Layer │ +│ ├─ CORS middleware │ +│ ├─ express.json() │ +│ │ │ +│ 2. Global Middleware Chain │ +│ ├─ requestContext │ +│ │ └─ Extracts: IP, User-Agent, Request-ID │ +│ │ │ +│ ├─ auditLoggerMiddleware │ +│ │ └─ Attaches audit logger to req.auditLogger │ +│ │ │ +│ 3. Route Matching │ +│ └─ /users → userRoutes │ +│ │ +│ 4. Route-Level Middleware (userRoutes.js) │ +│ ├─ jwtAuthenticate │ +│ │ ├─ Extracts token from Authorization header │ +│ │ ├─ Calls Auth Service API: │ +│ │ │ POST http://auth-service:3000/auth/validate-token +│ │ │ Body: { token: } │ +│ │ ├─ Auth Service validates: signature, expiry, etc. │ +│ │ └─ Sets req.user = { userId, role, ... } │ +│ │ │ +│ ├─ rateLimiterRead │ +│ │ └─ Checks rate limits (Redis-based) │ +│ │ │ +│ ├─ requireUserOrAdmin (coarseAuthorize) │ +│ │ └─ Verifies role is USER or ADMIN │ +│ │ │ +│ └─ fineAuthorize (for GET /:id only) │ +│ └─ Verifies user can read this specific user │ +│ (user can read own profile OR admin can read any)│ +│ │ +│ 5. Route Handler │ +│ └─ GET /:id handler │ +│ ├─ Extracts userId from req.params.id │ +│ ├─ Database Query (via queryHelper) │ +│ │ SELECT * FROM users WHERE id = :userId │ +│ └─ Returns user data │ +└─────────────────────────────────────────────────────────────┘ + ↓ +Response: { id, name, phone_number, avatar_url, ... } + ↓ +Mobile App +``` + +### Services & Dependencies + +1. **Backend Server** (Port 3200) + - Main API server + - Handles business logic + +2. **Auth Service** (Port 3000) + - JWT token validation endpoint: `/auth/validate-token` + - Called by `jwtAuthenticate` middleware + - Returns user payload if token is valid + +3. **Database** (PostgreSQL) + - Stores user data + - Queried via queryHelper + +4. **Redis** (Optional) + - Used for rate limiting + - Falls back gracefully if unavailable + +--- + +## Implementation Pattern for All APIs + +### Standard Middleware Chain Pattern + +All API routes should follow this consistent pattern: + +```javascript +// routes/exampleRoutes.js +import express from "express"; +import jwtAuthenticate from "../middleware/jwtAuthenticate.js"; +import { rateLimiterRead, rateLimiterWrite } from "../middleware/rateLimiter.js"; +import createCoarseAuthorize from "../middleware/coarseAuthorize.js"; +import { fineAuthorize } from "../middleware/fineAuthorize.js"; + +const router = express.Router(); + +// 1. Apply authentication to ALL routes +router.use(jwtAuthenticate); + +// 2. Apply rate limiting (read for GET, write for POST/PUT/DELETE) +router.use(rateLimiterRead); // Default: read rate limiter + +// 3. Apply coarse-grained authorization (role-based) +const requireUserOrAdmin = createCoarseAuthorize(['USER', 'ADMIN']); +router.use(requireUserOrAdmin); + +// 4. Define routes with fine-grained authorization where needed +router.get("/", async (req, res) => { + // No fine authorization - all authenticated users can list +}); + +router.get("/:id", + fineAuthorize({ + action: 'read', + resource: 'resource_type', // e.g., 'user', 'order', 'listing' + getResourceOwnerId: (req) => req.params.id, + }), + async (req, res) => { + // Handler logic + } +); + +router.post("/", + rateLimiterWrite, // Override default rate limiter for write operations + async (req, res) => { + // Handler logic + } +); + +router.put("/:id", + rateLimiterWrite, + fineAuthorize({ + action: 'write', + resource: 'resource_type', + getResourceOwnerId: (req) => req.params.id, + }), + async (req, res) => { + // Handler logic + } +); +``` + +### Middleware Execution Order + +``` +Request + ↓ +[Global Middleware - server.js] + 1. requestContext + 2. auditLoggerMiddleware + 3. CORS + 4. express.json() + ↓ +[Route Matching] + ↓ +[Route-Level Middleware - routes/*.js] + 1. jwtAuthenticate → Validates JWT, sets req.user + 2. rateLimiterRead/Write → Rate limiting + 3. coarseAuthorize → Role-based access (USER/ADMIN) + 4. fineAuthorize (optional) → Resource-level permissions + ↓ +[Route Handler] + ↓ +Response +``` + +### Quick Reference: Route Setup Checklist + +For each new API route file: + +- [ ] Import required middleware +- [ ] Apply `jwtAuthenticate` to all routes (router.use) +- [ ] Apply appropriate rate limiter (router.use or per-route) +- [ ] Apply coarse authorization (router.use) +- [ ] Add fine authorization for resource-specific routes +- [ ] Add audit logging in handlers (req.auditLogger) +- [ ] Handle errors appropriately + +### Example: New Resource Route + +```javascript +// routes/productsRoutes.js +import express from "express"; +import jwtAuthenticate from "../middleware/jwtAuthenticate.js"; +import { rateLimiterRead, rateLimiterWrite } from "../middleware/rateLimiter.js"; +import createCoarseAuthorize from "../middleware/coarseAuthorize.js"; +import { fineAuthorize } from "../middleware/fineAuthorize.js"; +import { select, insert, update } from "../db/queryHelper/index.js"; + +const router = express.Router(); + +// Authentication & Authorization +router.use(jwtAuthenticate); +router.use(rateLimiterRead); +router.use(createCoarseAuthorize(['USER', 'ADMIN'])); + +// GET /products - List all (public listings, no fine auth needed) +router.get("/", async (req, res) => { + try { + const products = await select({ table: 'products', where: { deleted: false } }); + res.json(products); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } +}); + +// GET /products/:id - Get specific product +router.get("/:id", + fineAuthorize({ + action: 'read', + resource: 'product', + getResourceOwnerId: (req) => req.params.id, + }), + async (req, res) => { + try { + const product = await select({ + table: 'products', + where: { id: req.params.id, deleted: false }, + limit: 1, + }); + if (product.length === 0) return res.status(404).json({ error: "Not found" }); + res.json(product[0]); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } + } +); + +// POST /products - Create product (requires write rate limiter) +router.post("/", + rateLimiterWrite, + async (req, res) => { + try { + const product = await insert({ + table: 'products', + data: req.body, + returning: '*', + }); + res.status(201).json(product); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } + } +); + +// PUT /products/:id - Update product +router.put("/:id", + rateLimiterWrite, + fineAuthorize({ + action: 'write', + resource: 'product', + getResourceOwnerId: (req) => req.params.id, + }), + async (req, res) => { + try { + const updated = await update({ + table: 'products', + data: req.body, + where: { id: req.params.id, deleted: false }, + returning: '*', + }); + if (updated.length === 0) return res.status(404).json({ error: "Not found" }); + res.json(updated[0]); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } + } +); + +export default router; +``` + +--- + +## Summary + +**All API requests follow this pattern:** +1. Mobile App sends request with JWT token +2. Backend receives request → Global middleware → Route middleware → Handler +3. JWT validated via Auth Service +4. Rate limiting applied +5. Authorization checks (coarse + fine) +6. Business logic executes +7. Response returned to app + +**Key Principles:** +- JWT authentication is mandatory for all protected routes +- Rate limiting prevents abuse +- Coarse authorization checks roles (USER/ADMIN) +- Fine authorization checks resource-level permissions +- Audit logging tracks all operations + diff --git a/middleware/coarseAuthorize.js b/middleware/coarseAuthorize.js index ac08516..e030cf7 100644 --- a/middleware/coarseAuthorize.js +++ b/middleware/coarseAuthorize.js @@ -97,22 +97,39 @@ function normalizeRole(role) { function createCoarseAuthorize(allowedRoles = null) { return function coarseAuthorize(req, res, next) { try { + console.log(`[Coarse Auth] Starting authorization check for ${req.method} ${req.path}`); + // User should be set by jwtAuthenticate middleware if (!req.user || !req.user.userId) { + console.log(`[Coarse Auth] ❌ FAILED: No user found in request. req.user:`, req.user); return res.status(401).json({ error: 'Unauthorized', message: 'Authentication required', }); } + console.log(`[Coarse Auth] User found:`, { + userId: req.user.userId, + role: req.user.role, + userType: req.user.userType, + }); + const userRole = normalizeRole(req.user.role); // Determine required roles for this route const requiredRoles = allowedRoles || getRequiredRoles(req.path); const normalizedRequiredRoles = requiredRoles.map(normalizeRole); + console.log(`[Coarse Auth] Role check:`, { + userRole, + requiredRoles: normalizedRequiredRoles, + routePath: req.path, + }); + // Check if user's role is in the allowed roles list if (!normalizedRequiredRoles.includes(userRole)) { + console.log(`[Coarse Auth] ❌ FAILED: User role "${userRole}" not in required roles:`, normalizedRequiredRoles); + // Log authorization failure if (req.auditLogger) { req.auditLogger.log({ @@ -133,9 +150,10 @@ function createCoarseAuthorize(allowedRoles = null) { }); } + console.log(`[Coarse Auth] ✅ Authorization passed: User role "${userRole}" is allowed`); next(); } catch (err) { - console.error('Authorization error:', err); + console.error('[Coarse Auth] ❌ Authorization error:', err); return res.status(500).json({ error: 'Internal server error', message: 'Authorization check failed', diff --git a/middleware/fineAuthorize.js b/middleware/fineAuthorize.js index cb00ce4..8bd3b2c 100644 --- a/middleware/fineAuthorize.js +++ b/middleware/fineAuthorize.js @@ -154,9 +154,21 @@ function authorizeLocationAction({ user, action, resourceOwnerId }) { export function fineAuthorize({ action, resource, getResourceOwnerId, getResourceData }) { return async function(req, res, next) { try { + console.log(`[Fine Auth] Starting fine-grained authorization for ${req.method} ${req.path}`); + console.log(`[Fine Auth] Action: ${action}, Resource: ${resource}`); + console.log(`[Fine Auth] User:`, req.user ? { + userId: req.user.userId, + role: req.user.role, + } : 'NOT SET'); + const resourceOwnerId = getResourceOwnerId ? await getResourceOwnerId(req) : null; const resourceData = getResourceData ? await getResourceData(req) : {}; + console.log(`[Fine Auth] Resource details:`, { + resourceOwnerId, + resourceData: Object.keys(resourceData), + }); + const result = authorizeAction({ user: req.user, action, @@ -165,11 +177,18 @@ export function fineAuthorize({ action, resource, getResourceOwnerId, getResourc resourceData, }); + console.log(`[Fine Auth] Authorization result:`, { + authorized: result.authorized, + reason: result.reason || 'N/A', + }); + if (!result.authorized) { + console.log(`[Fine Auth] ❌ FAILED: ${result.reason || 'Access denied'}`); + // Log authorization failure if (req.auditLogger) { req.auditLogger.log({ - userId: req.user.userId, + userId: req.user?.userId, action: 'fine_authorization_failed', route: req.path, status: 'forbidden', @@ -188,9 +207,10 @@ export function fineAuthorize({ action, resource, getResourceOwnerId, getResourc }); } + console.log(`[Fine Auth] ✅ Authorization passed`); next(); } catch (err) { - console.error('Fine authorization error:', err); + console.error('[Fine Auth] ❌ Fine authorization error:', err); return res.status(500).json({ error: 'Internal server error', message: 'Authorization check failed', diff --git a/middleware/jwtAuthenticate.js b/middleware/jwtAuthenticate.js index 15b5bac..2d6530d 100644 --- a/middleware/jwtAuthenticate.js +++ b/middleware/jwtAuthenticate.js @@ -23,6 +23,7 @@ const AUTH_SERVICE_TIMEOUT = parseInt(process.env.AUTH_SERVICE_TIMEOUT || '5000' */ async function validateTokenViaAuthService(token) { try { + console.log(`[JWT Auth] Calling auth service to validate token...`); const response = await axios.post( `${AUTH_SERVICE_URL}/auth/validate-token`, { token }, @@ -34,12 +35,16 @@ async function validateTokenViaAuthService(token) { } ); + console.log(`[JWT Auth] Auth service responded with status: ${response.status}`); + if (response.data.valid === true) { + console.log(`[JWT Auth] Auth service confirmed token is valid`); return { valid: true, payload: response.data.payload, }; } else { + console.log(`[JWT Auth] Auth service reported token as invalid:`, response.data.error); return { valid: false, error: response.data.error || 'Token validation failed' @@ -49,21 +54,21 @@ async function validateTokenViaAuthService(token) { // Handle different error types if (err.response) { // Auth service returned an error response - console.error('Auth service validation error:', err.response.status, err.response.data); + console.error(`[JWT Auth] ❌ Auth service validation error:`, err.response.status, err.response.data); return { valid: false, error: err.response.data?.error || 'Token validation failed' }; } else if (err.request) { // Request was made but no response received - console.error('Auth service unavailable:', err.message); + console.error(`[JWT Auth] ❌ Auth service unavailable:`, err.message); return { valid: false, error: 'Authentication service unavailable' }; } else { // Error setting up request - console.error('Error calling auth service:', err.message); + console.error(`[JWT Auth] ❌ Error calling auth service:`, err.message); return { valid: false, error: 'Failed to validate token' @@ -78,21 +83,34 @@ async function validateTokenViaAuthService(token) { * Calls auth service to validate token and authorize the request */ async function jwtAuthenticate(req, res, next) { + console.log(`[JWT Auth] Starting authentication check for ${req.method} ${req.path}`); + console.log(`[JWT Auth] Headers received:`, { + authorization: req.headers.authorization ? 'Bearer ' : 'MISSING', + 'content-type': req.headers['content-type'], + 'user-agent': req.headers['user-agent']?.substring(0, 50), + }); + // Extract token from Authorization header const authHeader = req.headers.authorization || ''; const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null; if (!token) { + console.log(`[JWT Auth] ❌ FAILED: No token found in Authorization header`); + console.log(`[JWT Auth] Auth header value: "${authHeader}"`); return res.status(401).json({ error: 'Unauthorized', message: 'Missing Authorization header. Expected format: Authorization: Bearer ' }); } + console.log(`[JWT Auth] Token found (length: ${token.length}), validating via auth service...`); + console.log(`[JWT Auth] Auth service URL: ${AUTH_SERVICE_URL}/auth/validate-token`); + // Validate token via auth service API const validationResult = await validateTokenViaAuthService(token); if (!validationResult.valid) { + console.log(`[JWT Auth] ❌ FAILED: Token validation failed - ${validationResult.error}`); // Log failed authentication attempt if (req.auditLogger) { req.auditLogger.logFailure('authenticate', validationResult.error || 'Token validation failed'); @@ -106,6 +124,14 @@ async function jwtAuthenticate(req, res, next) { } const payload = validationResult.payload; + console.log(`[JWT Auth] ✅ Token validated successfully`); + console.log(`[JWT Auth] Token payload:`, { + userId: payload.sub, + role: payload.role, + userType: payload.user_type, + phoneNumber: payload.phone_number ? '***' : null, + tokenVersion: payload.token_version, + }); // Extract user information from auth service response req.user = { @@ -119,6 +145,12 @@ async function jwtAuthenticate(req, res, next) { tenantId: payload.tenant_id || null, }; + console.log(`[JWT Auth] ✅ User authenticated:`, { + userId: req.user.userId, + role: req.user.role, + userType: req.user.userType, + }); + // Log successful authentication if (req.auditLogger) { req.auditLogger.logSuccess('authenticate', { userId: req.user.userId }); diff --git a/package-lock.json b/package-lock.json index 693da58..4930a49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.7.9", "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0", @@ -16,6 +17,7 @@ "knex": "^3.1.0", "node-cron": "^4.2.1", "pg": "^8.16.3", + "redis": "^4.7.0", "socket.io": "^4.8.1" } }, @@ -330,6 +332,65 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "optional": true }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -567,8 +628,55 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "optional": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/axios/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/axios/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -682,6 +790,15 @@ "node": ">=12" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -710,7 +827,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "optional": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -795,7 +911,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "optional": true, "engines": { "node": ">=0.4.0" } @@ -993,7 +1108,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "optional": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -1185,6 +1299,26 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", @@ -1293,6 +1427,15 @@ "node": ">=14" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1450,7 +1593,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "optional": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -2221,6 +2363,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -2283,6 +2431,23 @@ "node": ">= 10.13.0" } }, + "node_modules/redis": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz", + "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.1", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/routes/userRoutes.js b/routes/userRoutes.js index d7b6e18..1a1dbc1 100644 --- a/routes/userRoutes.js +++ b/routes/userRoutes.js @@ -17,6 +17,17 @@ router.use(requireUserOrAdmin); // 1. CREATE User (Requires write rate limiter) router.post("/", rateLimiterWrite, async (req, res) => { + console.log(`[User Route] POST /users - Request received`); + console.log(`[User Route] Authenticated user:`, req.user ? { + userId: req.user.userId, + role: req.user.role, + } : 'NOT AUTHENTICATED'); + console.log(`[User Route] Request body:`, { + id: req.body?.id, + name: req.body?.name, + phone_number: req.body?.phone_number ? '***' : undefined, + }); + try { // Parse and extract user data from request body const { @@ -128,6 +139,13 @@ router.get("/:id", getResourceOwnerId: (req) => req.params.id, }), async (req, res) => { + console.log(`[User Route] GET /users/:id - Request received`); + console.log(`[User Route] User ID requested: ${req.params.id}`); + console.log(`[User Route] Authenticated user:`, req.user ? { + userId: req.user.userId, + role: req.user.role, + } : 'NOT AUTHENTICATED'); + try { const { id } = req.params;