security: harden production configuration and routes
Run Tests on Branches / Detect Changes (push) Successful in 12s
Run Tests on Branches / Frontend Tests (push) Successful in 2m12s
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Successful in 2m2s
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
Run Tests on Branches / Backend Tests (push) Successful in 2m8s

This commit is contained in:
Antoni Nuñez Romeu
2026-07-22 17:24:54 +02:00
parent f60af5f6b2
commit 849763896d
21 changed files with 638 additions and 68 deletions
@@ -0,0 +1,30 @@
import { describe, expect, test } from '@jest/globals'
import { validateProductionEnv } from '../src/config/required-env.js'
describe('validateProductionEnv', () => {
test('rejects a missing production session secret', () => {
expect(() => validateProductionEnv({
NODE_ENV: 'production',
CORS_ORIGIN: 'https://app.example.com',
PG_URL: 'postgres://app:password@db/app',
})).toThrow(/SESSION_SECRET/i)
})
test('rejects placeholder production configuration', () => {
expect(() => validateProductionEnv({
NODE_ENV: 'production',
SESSION_SECRET: 'farma-clic-secret-key-change-in-production',
CORS_ORIGIN: 'http://localhost:3000',
PG_URL: 'postgres://app:password@db/app',
})).toThrow(/placeholder|production/i)
})
test('accepts complete non-placeholder production configuration', () => {
expect(() => validateProductionEnv({
NODE_ENV: 'production',
SESSION_SECRET: 'a-test-only-long-session-secret',
CORS_ORIGIN: 'https://app.example.com',
PG_URL: 'postgres://app:password@db/app',
})).not.toThrow()
})
})
+5 -3
View File
@@ -26,6 +26,9 @@ import multer from 'multer';
import { searchMedicines, getMedicineDetails, searchOTC } from './cima-service.js';
import { runFarmaciaWebhookImport, DEFAULT_FARMACIAS_WEBHOOK, importPharmaciesFromRows } from './farmacias-webhook-import.js';
import { fetchPharmaciesExternal } from '../API/index.js';
import { validateProductionEnv } from './src/config/required-env.js';
validateProductionEnv();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -81,11 +84,11 @@ if (PG_URL) {
}
const sessionConfig = {
secret: process.env.SESSION_SECRET || 'farma-clic-secret-key-change-in-production',
secret: process.env.SESSION_SECRET || (process.env.NODE_ENV === 'test' ? 'test-only-session-secret' : undefined),
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.COOKIE_SECURE === 'true',
secure: process.env.NODE_ENV === 'production' || process.env.COOKIE_SECURE === 'true',
sameSite: 'lax',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
@@ -2648,4 +2651,3 @@ if (process.env.NODE_ENV !== 'test') {
});
});
}
+30
View File
@@ -0,0 +1,30 @@
const PLACEHOLDERS = new Set([
'',
'change-me-in-production',
'farma-clic-secret-key-change-in-production',
'replace-me',
'dev-ingest-key-change-me',
])
export function validateProductionEnv(env = process.env) {
if (env.NODE_ENV !== 'production') return
const required = [
['SESSION_SECRET', env.SESSION_SECRET],
['CORS_ORIGIN', env.CORS_ORIGIN],
]
for (const [name, value] of required) {
if (!value || PLACEHOLDERS.has(value)) {
throw new Error(`${name} must be set to a non-placeholder value in production`)
}
}
if (env.CORS_ORIGIN.includes('localhost') || env.CORS_ORIGIN.includes('127.0.0.1')) {
throw new Error('CORS_ORIGIN must not point to localhost in production')
}
if (!env.PG_URL && (!env.PG_PASSWORD || PLACEHOLDERS.has(env.PG_PASSWORD))) {
throw new Error('PG_URL or PG_PASSWORD must be set to a non-placeholder value in production')
}
}