feat(security): harden the API, trust Hiboutik for deletions, lock syncs #3

Merged
quentin_duwa merged 4 commits from feature/input-validation into main 2026-11-08 09:36:50 +00:00

4 Commits

Author SHA1 Message Date
MastersGame
d8e05b1b3a feat(sync): update jsdoc 2026-11-08 10:34:50 +01:00
MastersGame
96fca40847 refactor(sync): rely on the advisory lock only and make syncAll private
Each startSync request takes the Postgres advisory lock on its own
connection (its own session), so it already refuses concurrent calls on
the same instance: the in-process syncInProgress flag and its
isSyncInProgress getter were redundant and are removed. syncAll becomes
private: syncs start only through startSync.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 10:21:27 +01:00
MastersGame
05911f4230 feat(security): harden the API and lock synchronizations across instances
- Validate the environment at startup (src/config): required variables
  must be present, HIBOUTIK_BASE_URL must be HTTPS.
- Global ValidationPipe (whitelist, forbidNonWhitelisted, transform).
- Helmet, CORS disabled unless CORS_ORIGINS is set, TRUST_PROXY support,
  HTTP header/request timeouts, shutdown hooks.
- Rate limiting: 60 req/min/IP globally, 5 req/min on POST /sync.
- AllExceptionsFilter: unexpected errors are logged (message + stack,
  never the raw error object) and answered with a generic 500.
- Postgres advisory lock (dedicated connection) so only one
  synchronization runs at a time across all instances; released by
  Postgres itself if the process dies.
- Fix the Jest moduleNameMapper (unescaped dot also matched .cjs files).
- Document the input validation and security rules in CLAUDE.md.
- Add class-validator, class-transformer, helmet and @nestjs/throttler;
  npm audit fix refreshed package-lock.json.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 10:11:35 +01:00
MastersGame
30c56f9330 feat(sync): trust Hiboutik answers for deletions
A successful (2xx) answer from Hiboutik is now the truth, even an empty
list: records missing from it are deleted. The "empty list means API
issue" safety nets are removed.

Deletion stays safe because a failed call deletes nothing: HTTP errors
throw, and the new HiboutikClient.getList (used by getAllPages and the
non-paginated lists) also throws on a 2xx body that is not a list, so a
malformed answer is never mistaken for an empty list. A 2xx without body
is an empty list.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 10:11:34 +01:00