Files
picpeak/backend/__tests__/utils
Paul Nothaft 340d91bdd5 feat(security): harden .picpeak restore robustness — sessions, roles, sequences
Implements the three restore-hardening items deferred from the #811 Codex
review (all validated against a real Postgres, see __tests__/integration/
picpeakRestorePg.test.js). Backend-only; targets main (feature, not a backport).

1. Global session cutoff (utils/sessionCutoff.js). A restore reassigns admin/
   customer/event ids, so ANY pre-restore JWT can rebind to a different restored
   principal. Revoking just the importing token wasn't enough. importFromPicpeak
   now stamps a unix-second cutoff in app_settings after the restore commits, and
   adminAuth / galleryAuth / verifyGalleryAccess / customerAuth reject any token
   whose iat predates it (cached 30s → one in-memory compare on the hot path).
   The operator's forced re-login mints a token past the cutoff, so it passes.

2. Role preservation across an RBAC replace (captureOperatorRole /
   preserveOperatorRole). The operator's role + granted permission NAMES are
   captured before the wipe; after roles/role_permissions are replaced the role
   is resolved by NAME against the restored data, and re-created with its grants
   if the backup omits it — so a crafted or cross-instance backup can't silently
   downgrade or lock out the operator. reinjectCurrentAdmin now returns the
   operator's id so the row can be re-pointed at the resolved role.

3. Postgres identity-sequence resync (resyncSequences). batchInsert writes
   explicit ids without advancing the sequences, so the next natural insert into
   any restored table collided on the PK. Runs AFTER commit (setval isn't
   transactional) and guards every table with a column-existence check —
   pg_get_serial_sequence RAISES on id-less tables like role_permissions.
   No-op on SQLite.

Tests: SQLite unit tests for the cutoff and role preservation; a gated Postgres
integration suite (npm run test:pg with PICPEAK_PG_TEST_URL) covering sequence
resync, the id-less-table guard, explicit-id reinject, role re-creation, and a
full cross-instance replaceAllTables run asserting operator preservation, role
re-establishment, FK integrity, and collision-free post-restore inserts.

Stacks on #811 (shares the reinject hardening); merge after it.
2026-07-16 12:56:34 +02:00
..