081f3edcdf
Auth/access-control audit fixes (all pre-existing on main; none are regressions). Verified end-to-end where noted. HIGH - Thumbnail enumeration: photoAuth granted any gallery token access to any flat /thumbnails/thumb_* file, so a visitor to one gallery could enumerate another (password-protected) gallery's entire thumbnail set. Scope thumbnail access to the token's event via photos.thumbnail_path. Live-verified: cross-event fetch now 404s, own-event still 200s. - Bulk ownership bypass: bulk-archive/bulk-delete acted on body-supplied event ids with no owner filter (single-event routes enforce requireEventOwnership), letting admin/editor archive or cascade-delete any event. Add filterOwnedEventIds; also guard rename + import-external; tighten photo-retry to scope admin (not just editor). Fix misleading bulk-delete comment. MED - verifyGalleryAccess never checked decoded.type — assert 'gallery' instead of relying on other token types incidentally lacking eventId. - secure-images generate-token/secure-download missing denySlideshowToken (#646 bypass): a leaked slideshow token could download originals. - Frontend: AuthenticatedImage + api.ts attached the gallery bearer token to absolute/external URLs — only attach to relative same-app paths. LOW hardening - Pin algorithms:['HS256'] on all auth-boundary jwt.verify calls. - crypto.timingSafeEqual for share-token + HMAC compares (utils/timingSafe). - Remove dead photoAuth import in galleryFeedback. Tests: new regression suites for thumbnail scoping + filterOwnedEventIds; fixed verifyGalleryAccess.customerRevoke fixture (real customer tokens carry type:'gallery'). Full backend suite at the pre-existing baseline (5 suites/27 tests fail on main too), zero new failures.
23 lines
667 B
JavaScript
23 lines
667 B
JavaScript
const crypto = require('crypto');
|
|
|
|
/**
|
|
* Constant-time string comparison for secrets (share tokens, HMAC
|
|
* signatures, etc.). Returns false for non-strings or length mismatch
|
|
* without leaking timing beyond the (non-secret) length. Prevents an
|
|
* attacker from recovering a token byte-by-byte via response-time
|
|
* differences of a naive `a === b`.
|
|
*/
|
|
function timingSafeEqualStr(a, b) {
|
|
if (typeof a !== 'string' || typeof b !== 'string') {
|
|
return false;
|
|
}
|
|
const ab = Buffer.from(a);
|
|
const bb = Buffer.from(b);
|
|
if (ab.length !== bb.length) {
|
|
return false;
|
|
}
|
|
return crypto.timingSafeEqual(ab, bb);
|
|
}
|
|
|
|
module.exports = { timingSafeEqualStr };
|