Enhance CORS and CSP configurations in Express app
This commit is contained in:
@@ -5,17 +5,65 @@ import fs from "fs"
|
||||
import { fileURLToPath } from "url"
|
||||
import { dirname } from "path"
|
||||
import { MetersCollection, MeterFeature } from "shared-types/meters"
|
||||
import { randomBytes } from "crypto"
|
||||
|
||||
// ES module equivalent of __dirname
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const app: Express = express()
|
||||
// Config
|
||||
const PORT = process.env.PORT || 3001
|
||||
const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:5173"
|
||||
|
||||
// Middleware
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
// Create app factory so tests can import without starting the server
|
||||
function createApp(): Express {
|
||||
const app: Express = express()
|
||||
|
||||
// Restrict CORS to the predefined frontend URL
|
||||
app.use(
|
||||
cors({
|
||||
origin: FRONTEND_URL,
|
||||
})
|
||||
)
|
||||
app.use(express.json())
|
||||
|
||||
// Require Origin header and reject mismatched origins
|
||||
app.use((req, res, next) => {
|
||||
const origin = req.headers.origin as string | undefined
|
||||
if (!origin) {
|
||||
return res.status(403).json({ error: "Missing Origin header" })
|
||||
}
|
||||
if (origin !== FRONTEND_URL) {
|
||||
return res.status(403).json({ error: "Forbidden origin" })
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
// Per-request CSP nonce and header. Also expose nonce via X-CSP-Nonce so the frontend
|
||||
// can apply it to inline scripts/styles when needed.
|
||||
app.use((req, res, next) => {
|
||||
const nonce = randomBytes(16).toString("base64")
|
||||
|
||||
const csp = [
|
||||
`default-src 'self' ${FRONTEND_URL}`,
|
||||
`connect-src 'self' ${FRONTEND_URL}`,
|
||||
`img-src 'self' data: ${FRONTEND_URL}`,
|
||||
`script-src 'self' 'nonce-${nonce}' ${FRONTEND_URL}`,
|
||||
`style-src 'self' 'nonce-${nonce}' ${FRONTEND_URL}`,
|
||||
].join("; ")
|
||||
|
||||
res.setHeader("Content-Security-Policy", csp)
|
||||
// Expose nonce so frontend templates can use it for inline scripts/styles
|
||||
res.setHeader("X-CSP-Nonce", nonce)
|
||||
// Vary on Origin so caches consider the Origin header when caching responses
|
||||
res.setHeader("Vary", "Origin")
|
||||
next()
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
const app = createApp()
|
||||
|
||||
// Route to serve the saudi_meters.json file
|
||||
app.get("/api/meters", (req, res) => {
|
||||
@@ -71,10 +119,14 @@ app.get("/", (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on http://localhost:${PORT}`)
|
||||
console.log(`📊 Meters data available at http://localhost:${PORT}/api/meters`)
|
||||
})
|
||||
// Start the server only when run directly (not when imported by tests)
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on http://localhost:${PORT}`)
|
||||
console.log(
|
||||
`📊 Meters data available at http://localhost:${PORT}/api/meters`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default app
|
||||
|
||||
Reference in New Issue
Block a user