295 lines
9.6 KiB
Markdown
295 lines
9.6 KiB
Markdown
# 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 <JWT_TOKEN>
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 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: <JWT_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
|
|
|