feat(api): capa SAP con sesión compartida, mutex de login y cola de peticiones
- SapSessionService: una sesión para todos; un solo login aunque lleguen peticiones concurrentes o 401 simultáneos (loginInFlight); logout al apagar. - SapClientService: proxy con reintento único ante 401, errores SAP traducidos (SapHttpException) y fallos de red/TLS mapeados a 502/504. Un login rechazado se informa como 502, nunca 401 (el frontend lo toma como sesión web expirada). - Confianza TLS: SAPB1_SL_CA_FILE opcional (autofirmado de SAP o CA de firewall) sumado a las CA del sistema; VERIFY_SSL=true por defecto y no se puede apagar en producción. - SAPB1_SL_MAX_CONCURRENCY (default 1): medido en SAP10, más de ~2 peticiones en paralelo retrasan ~7 s cada una extra; en serie ~60 ms. - GET /api/health/sap, tests unitarios (MockAgent) y e2e opcional contra SAP real (SAP_LIVE=1). apps/api/certs documentado; *.pem/*.cer ignorados. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
445efc998d
commit
bb4acb5510
19 files changed
+629
-22
No files matched your search
@@ -1,20 +1,61 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const envSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
PORT: z.coerce.number().int().positive().default(3000),
|
||||
// Orígenes permitidos por CORS, separados por coma. Vacío = mismo origen.
|
||||
CORS_ORIGINS: z
|
||||
.string()
|
||||
.default('')
|
||||
.transform((v) =>
|
||||
v
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
LOG_LEVEL: z.enum(['error', 'warn', 'log', 'debug', 'verbose']).default('log'),
|
||||
});
|
||||
const emptyToUndefined = (v: unknown) => (v === '' ? undefined : v);
|
||||
|
||||
export const envSchema = z
|
||||
.object({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
PORT: z.coerce.number().int().positive().default(3000),
|
||||
// Orígenes permitidos por CORS, separados por coma. Vacío = mismo origen.
|
||||
CORS_ORIGINS: z
|
||||
.string()
|
||||
.default('')
|
||||
.transform((v) =>
|
||||
v
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
LOG_LEVEL: z.enum(['error', 'warn', 'log', 'debug', 'verbose']).default('log'),
|
||||
|
||||
// --- SAP Business One Service Layer ---
|
||||
SAPB1_SL_URL: z
|
||||
.url()
|
||||
.transform((v) => v.replace(/\/+$/, '')),
|
||||
SAPB1_SL_COMPANYDB: z.string().min(1),
|
||||
SAPB1_SL_USER: z.string().min(1),
|
||||
SAPB1_SL_PASSWORD: z.string().min(1),
|
||||
SAPB1_SL_TIMEOUT_MS: z.coerce.number().int().positive().default(15000),
|
||||
// Peticiones simultáneas permitidas hacia el Service Layer; el resto se encola.
|
||||
// Medido en SAP10: con más de ~2 en paralelo el Service Layer retrasa ~7 s cada
|
||||
// petición extra, mientras que en serie cada una tarda ~60 ms. Default: 1.
|
||||
SAPB1_SL_MAX_CONCURRENCY: z.coerce.number().int().positive().default(1),
|
||||
// CA adicional de confianza (PEM): certificado autofirmado del Service
|
||||
// Layer o CA de un firewall que inspecciona SSL. Opcional.
|
||||
SAPB1_SL_CA_FILE: z.preprocess(emptyToUndefined, z.string().optional()),
|
||||
SAPB1_SL_VERIFY_SSL: z
|
||||
.enum(['true', 'false'])
|
||||
.default('true')
|
||||
.transform((v) => v === 'true'),
|
||||
})
|
||||
.superRefine((env, ctx) => {
|
||||
if (env.SAPB1_SL_CA_FILE && !existsSync(resolve(env.SAPB1_SL_CA_FILE))) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: ['SAPB1_SL_CA_FILE'],
|
||||
message: `no existe el archivo ${resolve(env.SAPB1_SL_CA_FILE)}`,
|
||||
});
|
||||
}
|
||||
if (env.NODE_ENV === 'production' && !env.SAPB1_SL_VERIFY_SSL) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: ['SAPB1_SL_VERIFY_SSL'],
|
||||
message: 'no se permite desactivar la validación SSL en producción; use SAPB1_SL_CA_FILE',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user