fix architecture and write new tests
This commit is contained in:
+41
-11
@@ -4,8 +4,9 @@ import helmet from 'helmet'
|
||||
import morgan from 'morgan'
|
||||
import swaggerUi from 'swagger-ui-express'
|
||||
import { openApiDocument } from './swagger/openapi'
|
||||
import { getLegacyStorageRoots, getStorageRoot } from './lib/storage'
|
||||
import { getPublicStorageRoot } from './lib/storage'
|
||||
import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter'
|
||||
import { requestIdMiddleware } from './middleware/requestId'
|
||||
|
||||
// ─── Module routes ────────────────────────────────────────────
|
||||
import webhookRouter from './modules/webhooks/webhook.routes'
|
||||
@@ -78,6 +79,15 @@ export function createApp() {
|
||||
app.set('trust proxy', 1)
|
||||
}
|
||||
|
||||
app.use(requestIdMiddleware)
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.headers['x-middleware-subrequest']) {
|
||||
return res.status(400).json({ error: 'bad_request', message: 'Unsupported internal request header', statusCode: 400 })
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
app.use(corsMiddleware)
|
||||
|
||||
// Customer identity documents must never be anonymously retrievable from the
|
||||
@@ -95,24 +105,43 @@ export function createApp() {
|
||||
next()
|
||||
})
|
||||
|
||||
// Serve uploaded assets from the configured storage root, with a legacy fallback
|
||||
// for older files that were written under the previous API-local path.
|
||||
app.use('/storage', express.static(getStorageRoot()))
|
||||
for (const legacyRoot of getLegacyStorageRoots()) {
|
||||
app.use('/storage', express.static(legacyRoot))
|
||||
}
|
||||
// Serve only explicitly public uploaded assets. Private documents are resolved
|
||||
// through authenticated API routes such as /customers/:id/license-image.
|
||||
app.use('/storage', express.static(getPublicStorageRoot()))
|
||||
|
||||
// Swagger UI — mounted before helmet so its assets are not blocked by CSP
|
||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument, { customSiteTitle: 'RentalDriveGo API Docs' }))
|
||||
app.get('/api/v1/openapi.json', (_req, res) => res.json(openApiDocument))
|
||||
|
||||
// Webhook must use raw body BEFORE express.json()
|
||||
app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter)
|
||||
// Webhooks must use raw body BEFORE express.json(); signature verification
|
||||
// must never reconstruct the payload with JSON.stringify(req.body).
|
||||
app.use(`${v1}/webhooks`, express.raw({ type: 'application/json' }), webhookRouter)
|
||||
app.use(`${v1}/payments/webhooks`, express.raw({ type: 'application/json' }))
|
||||
app.use(`${v1}/subscriptions/webhooks`, express.raw({ type: 'application/json' }))
|
||||
|
||||
// Let /storage responses manage CORP explicitly so missing files still return
|
||||
// a normal cross-origin 404 instead of being blocked by Helmet's default
|
||||
// same-origin policy.
|
||||
app.use(helmet({ crossOriginResourcePolicy: false }))
|
||||
// same-origin policy. Keep CSP explicit instead of letting browser security
|
||||
// drift into wishful thinking with headers.
|
||||
app.use(helmet({
|
||||
crossOriginResourcePolicy: false,
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
baseUri: ["'self'"],
|
||||
frameAncestors: ["'none'"],
|
||||
formAction: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
scriptSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
||||
connectSrc: ["'self'", 'https:', 'wss:'],
|
||||
upgradeInsecureRequests: process.env.NODE_ENV === 'production' ? [] : null,
|
||||
},
|
||||
},
|
||||
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
||||
frameguard: { action: 'deny' },
|
||||
}))
|
||||
if (process.env.NODE_ENV !== 'test') app.use(morgan('combined'))
|
||||
app.use(express.json({ limit: '10mb' }))
|
||||
|
||||
@@ -121,6 +150,7 @@ export function createApp() {
|
||||
app.use(`${v1}/auth/company`, authLimiter, companyAuthRouter)
|
||||
app.use(`${v1}/auth/employee`, authLimiter, employeeAuthRouter)
|
||||
|
||||
app.use(`${v1}/admin/auth`, authLimiter)
|
||||
app.use(`${v1}/admin`, adminLimiter, adminRouter)
|
||||
|
||||
app.use(`${v1}/marketplace`, publicLimiter, marketplaceRouter)
|
||||
|
||||
Reference in New Issue
Block a user