5db0a76cce94de03f86295ba2bd6ba526661d16d
1342 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
435c558704 |
chore(main): release 3.96.1-beta.0 (#898)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
ec66cd2684 |
fix(gallery): keep the lightbox toolbar from masking the photo (#888) (#892)
The bottom info/action bar was a translucent gradient overlaying the image, hiding the lower edge of the photo. The bar is now opaque and the image area stops above it (measured via ResizeObserver, since the bar height varies with flex-wrap, the optional filename line and safe-area padding), so the photo is always fully visible. Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
9f7c644e0a |
chore(main): release 3.96.0-beta.0 (#897)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
97f68899a2 |
feat(gallery): quick return from zoomed to fit-to-screen in the lightbox (#886) (#891)
Adds a fit-to-screen button next to the zoom controls (enabled while zoomed) and double-click-to-reset on the image itself. Both snap the photo back to 100% and re-centre it. Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
0f8b68c05c |
chore(main): release 3.95.5-beta.0 (#896)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
34c2992521 |
fix(gallery): don't close the lightbox when clicking beside the photo (#883) (#890)
Clicking the black bars around the image (a missed arrow click) closed the lightbox and dropped the guest back into the grid. The lightbox now only closes via the X button or Escape, matching what gallery guests expect while paging through photos. Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
d41cd9746d |
chore(main): release 3.95.4-beta.0 (#887)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
33f1bc42a9 |
fix: sync gallery feedback filters after lightbox like/rating in simple mode (#882)
In simple identity mode, likes and ratings submitted from the lightbox never called onFeedbackChange, so the gallery's photo list (whose like_count drives the Likes/Rated feedback filter chips) stayed stale until a full page reload. Liked photos were missing from the Likes filter; unliked photos stayed stuck in it. The guest-identity-mode paths and the grid PhotoCard paths already call onFeedbackChange after submitting - the simple-mode lightbox paths were the only ones missing it. Add the call to the three missing paths: submitLike (simple branch), submitRating (simple branch), and the FeedbackIdentityModal onSubmit handler. Verified locally (Docker build of main): like a photo in the lightbox after navigating with Next/Prev, open the Likes filter - the photo now appears immediately with no reload, and filter contents match the admin feedback API exactly. |
||
|
|
06a9991a22 |
chore(main): release 3.95.3-beta.0 (#880)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
ea8bd9b3e9 |
chore(main): release 3.95.2-beta.0 (#876)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
a2e723413e |
fix(backup): make backup settings actually apply (#871) (#874)
* fix(backup): make backup settings actually apply (#871) - Wire the What-to-Backup toggles into the walker: honor backup_include_thumbnails / backup_include_photos (opt-out, default ON) and accept the UI's backup_include_archives spelling for the archived gate (the engine expected _archived, so the Archives checkbox silently never worked). - Fix the 167.6 TB dashboard size: file_size_bytes is a bigint that node-postgres returns as a string, and the S3 path concatenated it onto the byte counter; coerce to Number at the source. - Compute the real next scheduled run (cron-parser) and return it as nextBackup; the UI read a field the API never sent and rendered a hardcoded 'Not scheduled'. A named schedule label now beats the stray default cron the UI always sent, which silently turned weekly schedules into daily 03:00 runs. - Never back up filesystem noise (.nfs* silly-renames, .DS_Store, Thumbs.db) and honor backup_exclude_patterns in the walker (previously rsync-only). - Remove the compression/encryption toggles from the configuration UI: no backend implementation exists, and collecting an encryption passphrase while uploading plaintext is a false promise. Closes #871 * fix(backup): close the review gaps in the settings wiring - The UI's backup_include_archives now beats the migration-seeded backup_include_archived: every install has the singular key seeded true, so the alias-only-when-absent lookup made unchecking Archives a no-op. - rsync destinations now receive the de-selected What-to-Backup paths and the noise filters as anchored --exclude args; previously rsync synced the whole storage root and the walker's selection only shaped the manifest, which then misreported what was actually transferred. - Escape regex metacharacters in the walker's glob matcher: '.nfs*' compiled to /^.nfs.*$/ whose leading dot matched any character, so files like anfs-photo.jpg were silently dropped from backups. - The Backup Coverage report now uses the same gate as the walker (new 'skipped-by-setting' status) instead of re-implementing it without the opt-out toggles and the archives alias. * fix(backup): make the coverage diagnostics agree with the walker - The coverage table shows the alias-aware flag value the gate actually used, instead of the seeded backup_include_archived shadowed by the UI's plural key (true next to a 'Gated off' badge). - skipped-by-setting paths are now counted in the coverage summary (backend, TS contract, summary card, EN/DE locales) so the totals reconcile again when Photos or Thumbnails is unchecked. - The form's thumbnail default now matches the backend's never-saved fallback (include): the checkbox no longer shows 'off' while thumbnails are being backed up, and saving an unrelated setting no longer flips the backup scope. * fix(backup): keep custom crons, exclude disabled rows from rsync, normalize flag display - Saving a named schedule no longer wipes the stored custom cron: the backend already prefers the label, so the cron field stays inert for named schedules and is preserved for switching back to Custom. A custom schedule now validates the 5-field expression before saving (the backend silently fell back to daily 02:00 on a blank value). - resolveExcludedBackupPaths now also returns rows disabled via include_in_default, so rsync excludes them; the enabled-only loader hid them and rsync transferred their contents anyway. - The coverage table normalizes flag values like the walker does — Boolean('false') displayed true beside a gated-off badge. |
||
|
|
0c65edd99a |
chore(main): release 3.95.1-beta.0 (#872)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
6e2e0a1a63 |
chore(main): release 3.95.0-beta.0 (#867)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
219d07b04a |
feat(auth): OIDC logout-to-IdP — phase 3 (#798) (#865)
* feat(auth): OIDC logout-to-IdP — phase 3 (#798) RP-initiated logout behind a new oidc_logout_from_idp setting: logging out of PicPeak also ends the IdP session. The SSO callback stores the raw ID token in an HttpOnly cookie (also the marker that the session came in via SSO — local-password sessions never bounce to the IdP); /logout builds the end_session URL from discovery metadata with id_token_hint + post_logout_redirect_uri + client_id and returns it as ssoLogoutUrl for the frontend to navigate to. Any failure (no end_session_endpoint, IdP unreachable, feature off) degrades to the plain local logout. Settings surface exposes the toggle plus the computed post-logout redirect URI to register at the IdP. Session timeouts deliberately stay local-only. 6 integration tests over the mock IdP; live-verified against Keycloak 26 (logout ends the Keycloak session, no confirmation prompt). * fix(auth): harden the SSO logout marker cookie (#798 phase 3) Codex review round 1: - Derive the oidc_id_token cookie options from the shared cookie policy (COOKIE_SAMESITE / COOKIE_DOMAIN / secure resolution) — hardcoded Lax meant split-origin deployments running on SameSite=None never sent the marker to the cross-site /logout XHR, silently disabling logout-to-IdP. - Oversized ID tokens (>3.9KB) now store a bare 'sso' marker instead of no cookie, so the claimed client_id-only end-session fallback actually happens; /logout only passes the value as id_token_hint when it is a real JWT. - establishAdminSession clears any stale marker on every fresh login — sessions can die without /logout (deactivation, expiry, restore), and a surviving marker would bounce a later local-password session to the IdP. The SSO callback re-sets the marker for its own session. Tests: oversized-token marker + hint-less end-session URL, stale-marker cleared on local login; helper updated for the clear+set cookie pair. * fix(auth): validate the logout hint against the current OIDC config (#798 phase 3) Codex review round 2: an ID token stored at login can outlive an issuer/client config change; sending it to the newly configured IdP as id_token_hint strands the user on the IdP's error page (providers validate iss/aud on the hint). buildEndSessionUrl now decodes the hint (no verification — routing only): different issuer → skip the round-trip entirely (the session belongs to another IdP); same issuer but changed client → keep the round-trip, drop the unusable hint. Two tests pin both paths. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
c5dc790e28 |
chore(main): release 3.94.2-beta.0 (#864)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
323dcae917 |
fix(gallery): block password form in Instagram in-app browser and unmask login errors (#863)
* fix(gallery): block password form in Instagram in-app browser (#654) Field reports show gallery password login still failing inside Instagram's IAB after the #656 input-attribute/trim defenses. Three changes: - Replace the advisory amber banner with a red blocking state: the password form is hidden in the Instagram IAB and replaced with platform-specific "open in external browser" instructions plus a copy-link button (clipboard API with execCommand fallback). A "try anyway" link restores the form as an escape hatch. - Stop masking non-password failures as "incorrect password": a request that never got a response (offline, webview killed it) now reports a connection error, and a reCAPTCHA 400 reports a verification failure — both previously fell through to the wrong-password message and sent guests chasing the wrong cause. - Strip invisible Unicode (zero-width chars, word joiner, BOM, soft hyphen) from the submitted password in addition to trimming — these ride along when the password is copy-pasted out of a chat app and fail byte-exact bcrypt compare server-side. * fix(gallery): retry login with typed password + honor execCommand result (#654) Codex review round 1: - Stored passwords can legitimately contain the invisible code points the sanitizer strips (e.g. ZWJ emoji sequences) — creation paths don't normalize. On a 401 where the sanitized form differs from the typed (trimmed) input, retry once with the typed value. Skipped when a reCAPTCHA token is in play (single-use). - document.execCommand('copy') signals failure via its return value, not by throwing — only show "Link copied" when it returns true. * fix(gallery): move invisible-char password fallback server-side (#654) Codex review round 2: the client-side retry either burned the single-use reCAPTCHA token (making exotic-but-valid passwords impossible to enter with reCAPTCHA on) or burned failed-attempt lockout quota on every rescued login. Doing the fallback as a second bcrypt compare inside the same gallery/verify request eliminates both: exact bytes are compared first (stored passwords containing e.g. ZWJ emoji keep working), the sanitized form only on mismatch, and trackFailedAttempt only fires when both fail. Frontend goes back to plain trim-on-submit; the client-side sanitizer util and retry are removed. 7 integration tests pin the contract. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
9ac23e5fe1 |
chore(main): release 3.94.1-beta.0 (#861)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
f89d374236 |
chore(main): release 3.94.0-beta.0 (#859)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
c6ec93eef9 |
fix(dates): normalize SQLite epoch timestamps at remaining API surfaces (#485 follow-up) (#857)
The audit #485 called for: on SQLite (native installs), timestamp columns written with a raw `new Date()` through knex store epoch-ms numbers; Postgres returns ISO strings. Frontend code written against Postgres calls parseISO() on them — parseISO(number) throws and crashes the page. #485 fixed admin Users and listed api tokens / photos / activity as out-of-scope follow-ups. Verified crash on main: Timeline gallery layout parseISO(uploaded_at) against photos written by the archive-RESTORE path (raw Date). Other raw-write surfaces (api_tokens last_used_at/revoked_at, email_queue) degrade rather than crash but violate the ISO contract. - extract toIso() from adminUsers.js into utils/dateNormalize.js (contract unchanged — the 10 existing #485 tests still pin it) - write-side: archive-restore uploaded_at, api-token last_used_at / revoked_at, email_queue created_at/sent_at now write ISO strings - read-side (heals existing corrupted rows): gallery /photos normalizes uploaded_at/captured_at; api-tokens list normalizes all four timestamp fields - frontend defence-in-depth: Timeline layout parses uploaded_at tolerantly (typeof guard) for stale caches / old backends - 2 regression tests seed literal epoch numbers and assert the API serves ISO strings activity_logs turned out safe (created_at comes from the DB default, not a raw Date) — left untouched. Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
2f05fcc39d |
feat(gallery): reveal mode — hide gallery from guests until reveal (#838) (#856)
* feat(gallery): reveal mode — hide gallery from guests until reveal (#838) Guests can upload during the event but see no photos until the host reveals the gallery, manually ("Reveal now") or at a scheduled time. - migration 165: events.reveal_mode / reveal_at / revealed_at. Effective visibility is computed at REQUEST time (reveal_at <= now opens the gate exactly on schedule); the minutely scheduler only stamps revealed_at durably and emits a gallery.revealed workflow trigger - server-side enforcement in gallery.js: /photos returns the event shell with photos: [] + hidden_until_reveal for plain guests; image/download/stats endpoints 403 with GALLERY_HIDDEN (photo IDs are sequential — listing-only gating would be probeable); feedback-summary gated too. Slideshow tokens (surprise beamer), client access and the admin preview bypass; the guest upload route stays open - admin: reveal toggle + optional scheduled datetime next to the guest upload settings, status line and "Reveal now" button on the overview; re-enabling the toggle clears revealed_at so a gallery can re-hide - guest UI: upload-only view (hero, friendly message, scheduled time, upload button) for every layout; i18n for all 8 locales - timestamps written as ISO strings — the SQLite driver stringifies raw Date objects into garbage; ISO round-trips on both engines - 14 integration tests over minted gallery/slideshow/client/admin tokens * fix(gallery): reveal/re-arm semantics + upload button i18n key (#838) - "Reveal now" also clears a pending reveal_at: the schedule is consumed, so the full-form admin save can't accidentally re-hide a revealed gallery with a stale future date - setting a FUTURE reveal_at on a revealed gallery re-arms hiding — the one intentional way to re-hide without double-toggling the mode - guest upload button uses the existing upload.uploadPhotos key (gallery.uploadPhotos never existed; the button showed EN everywhere) * fix(gallery): close reveal bypasses from review round 1 (#838) - the hero-derivative route and the secure-images token-mint + secure-download routes are now reveal-gated: hero serves a 1920px derivative of ANY sequential photo id and secure tokens fetch originals — both were open bypasses while hidden. blockHiddenGallery moved to utils/revealMode.js and shared - customer-portal tokens (via:'customer', no accessLevel) now bypass reveal mode — they are the host/customer, not a guest, and were getting the upload-only view - an open hidden guest view refetches exactly at reveal_at plus a 60s fallback poll, so the gallery appears without a manual reload - gallery.revealed added to the workflow editor's trigger picker so the advertised notification hook is reachable in the UI - migration 165 guards each column independently (partial-state safe) * fix(gallery): reveal round 2 — remaining bypass surfaces + lifecycle edges (#838) - legacy /api/images router reveal-gated (view, secure-token + signed-url minting), and the signed-URL SERVE path re-checks hidden state via a backward-compatible bypass flag in the token payload - secure-image tokens record revealBypass at mint and are re-validated at serve time — a re-hide kills in-flight guest tokens within the request, while slideshow/client tokens keep working - OG metadata and the unauthenticated /og cover fall back to the brand logo / 404 while hidden — no hero-photo spoiler for social crawlers - photo-feedback GET/POST reveal-gated (sequential ids were enumerable); /my-feedback returns the empty back-compat shape (rows leak filename + storage path) - the reveal scheduler skips drafts — no premature stamp/notification for unpublished galleries - emitWorkflowEvent gains an additive dedupSuffix; both reveal emitters pass the reveal timestamp so a re-hidden gallery's second reveal fires workflows again instead of deduping into silence * fix(gallery): reveal round 3 — schedule consumption + two-way client sync (#838) - the scheduler now consumes reveal_at when stamping (matching "Reveal now"), and re-arming via a partial API update clears a stale PAST schedule — previously {reveal_mode:true} without reveal_at could instantly re-open the gate through the leftover date - /photos exposes reveal_armed so an open VISIBLE gallery keeps a 60s poll while the mode is on — a re-hide now propagates to open clients in both directions, not just hidden→visible Codex round-3 claim about timestamp-without-timezone drift on non-UTC Postgres was verified FALSE: knex's table.timestamp() creates timestamptz on PG (confirmed via information_schema on a live install), which stores absolute instants regardless of server TZ. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
3d6c9848dc |
feat(feedback): emoji reactions on photos (#839) (#855)
* feat(feedback): emoji reactions on photos (#839) Per-photo emoji reactions from a fixed curated set (❤️ 😂 😍 👏 🎉), one reaction per guest per photo — same emoji toggles off, another switches in place. Stored as feedback_type='reaction' rows with per-guest scoping identical to likes (guest_id when present, device hash otherwise). - migration 164: allow_reactions toggle (default on, still gated by the opt-in feedback_enabled master switch), photo_feedback.reaction value column, denormalized photos.reaction_count - emoji whitelist enforced in the route validator AND the service (shared constants/reactions.js, mirrored in the frontend) - per-emoji tallies + my_feedback.reaction in the photo feedback endpoint; hidden-by-moderator reactions leave all counts - reactions ride the existing rate limiting (like-tier), guest identity modes, and moderation actions; long + pivot exports carry the emoji - gallery: reaction bar in the photo feedback panel (grid lightbox); admin: allow_reactions toggle next to likes, analytics tile, create/duplicate event paths - i18n for all 8 locales; 9 service-level tests * fix(feedback): reach reactions without comments; numeric analytics totals (#839) - the lightbox feedback-panel toggle was gated on allow_comments only — with comments off the new reaction bar was unreachable; the gate now opens for comments OR reactions - the analytics summary now coerces Postgres string counts to numbers: total_feedback concatenated instead of adding ("00006") * fix(feedback): harden reactions from review round 1 (#839) - per-emoji tallies are gated on show_feedback_to_guests — with sharing off a guest sees only their own selection, no aggregate counts - reaction toggle/switch operate on the guest-scoped row SET, so rows duplicated by the (like-parity) check-then-insert race collapse on the next interaction instead of counting twice - rate-limit defaults merge UNDER the persisted settings object — stored rows predating the reaction key otherwise dropped it to the generic 100/h fallback - optimistic revert uses the pre-mutation value via mutation context; the onError closure sees the post-optimistic render, so the old revert froze the wrong state on failed toggles * fix(feedback): review round 2 — hide reaction_count with sharing off, admin list shows emoji (#839) - summary.reaction_count is gated on show_feedback_to_guests like the per-emoji map, keeping the "no aggregates while sharing is off" promise consistent - the admin feedback list renders the reaction emoji on reaction rows and the type filter gains a Reactions option (7 locales; es has no types block and falls back to EN defaults) * fix(feedback): register reaction activity types with translated labels (#839) photo_reaction / guest_feedback_reaction are logged by the submission paths but were absent from the frontend activity-type union and the admin.activities label maps — the recent-activity feed would have shown the raw identifiers. All 8 locales. * feat(feedback): reactions in guest CRM and the premium gallery layout (#839) - guest CRM: per-guest reaction counts in the list aggregation and a Reacted tab (photo grid with emoji badges) + stats card in the guest detail modal; picks/aggregate/exports stay selection-only by design - premium layout: its own yet-another-react-lightbox now gets a fixed reaction-bar overlay (per-photo fetch, optimistic switch) — reactions were otherwise unreachable in this layout since it bypasses the shared PhotoLightbox - allowReactions threaded through the layout feedbackOptions; guest i18n keys for the 7 locales that carry the guests block * fix(feedback): portal the premium reaction bar to document.body (#839) Inside the layout tree an ancestor stacking context (framer-motion transforms) painted the bar under yarl's body-level portal — visible but unclickable, every tap landed on the slide image. As a direct body child the z-index 10000 genuinely wins over yarl's 9999. Verified by clicking through in the running app. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
f8a95d29d2 |
feat(auth): OIDC role mapping + login policy — phase 2 (#798) (#854)
* feat(auth): OIDC role mapping + login policy — phase 2 (#798) Role mapping: configurable dot-path roles claim (Keycloak realm_access.roles, Authentik/Pocket ID groups, Entra roles), IdP-value → PicPeak-role mapping table validated against the roles table, re-evaluated on every SSO login with highest-priority-wins on multiple matches. The last active super_admin is never demoted. Optional require-mapped-role policy refuses logins whose token maps to no role (sso_error=no_role). Login policy: oidc_disable_local_login makes the API refuse password logins (403 LOCAL_LOGIN_DISABLED) and the login page render SSO-only; only effective while SSO is enabled+configured, and OIDC_BREAK_GLASS=true always re-opens local login. Public settings expose the EFFECTIVE flag only. Settings UI: Role-mapping card (claim path, mapping rows editor, strict toggle) and Login-policy card with break-glass hint, EN+DE. 14 new integration tests over the mock IdP. * fix(auth): harden phase-2 review findings (#798) - memoize the scrypt-derived OIDC key and serve /public/settings from a 10s-TTL flag cache — the unauthenticated endpoint no longer pays a 13-key config read + blocking scryptSync per request (login route still checks uncached) - make the last-super-admin demotion guard atomic (FOR UPDATE on the active super rows) — concurrent mapped callbacks could previously both count 2 and demote both supers - own-property lookup in role mapping: IdP values like `constructor` now count as unmapped instead of corrupting the roles query - SsoTab clears oidc_disable_local_login in the same save that turns SSO off — the full-form payload otherwise hit the server-side 400 * fix(auth): guarantee break-glass reachability for SSO-only mode (#798) - wire OIDC_BREAK_GLASS and OIDC_ENCRYPTION_KEY through the quick-start docker-compose.yml env allowlist (production compose already passes .env via env_file) and document both in .env.example - refuse enabling oidc_disable_local_login unless an active local-password super_admin exists: OIDC_BREAK_GLASS only re-opens the password route, which OIDC-owned accounts can never use, and settings.edit is super_admin-only — an all-OIDC instance would be unrecoverable during an IdP outage * fix(auth): close SSO-only lockout gaps from review round 3 (#798) - role sync never demotes the last active LOCAL-password super_admin (an OIDC-owned super does not count as break-glass), and isLocalLoginDisabled() disarms itself when no such account remains — self-healing against manual demotion/deactivation/deletion paths - the local-super save-time check now validates the MERGED state, so re-enabling SSO with a stored disable flag is checked too - ALL oidc_* keys are reserved from the generic settings upserts/reads (prefix match) — policy and mapping invariants can only go through the validated PUT /sso - /admin/login/mfa re-checks the policy so an mfa_pending token minted before the flip cannot complete into a local session --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local> |
||
|
|
ad326da35c |
chore(main): release 3.93.0-beta.0 (#853)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
cb5b319f10 |
feat(notifications): surface guest activity in the admin bell (#849)
* feat(notifications): surface guest activity in the admin bell (#746) Favorites already reached activity_logs (feedbackService), but gallery opens and downloads only landed in access_logs — invisible in the notification bell. Now: - gallery_opened on the guest photo-list route, debounced in-memory to one notification per event per 6h (the endpoint fires per page load; per-hit notifications would spam the bell). Slideshow traffic stays excluded, matching the analytics exclusion. - gallery_downloaded on all four download paths (streamed + pre-zipped + presigned download-all, download-selected) with scope metadata. - Frontend: locale entries for galleryOpened/galleryDownloaded (and photoFavorite, which previously fell through to the generic 'system activity' line) in all 8 languages — resolved via the existing smart camelCase fallback, no switch cases needed. Distinct bell icons per type. * fix(notifications): single-photo download activity + render per-type bell icons (codex review of #849) - The per-photo Save route (GET /:slug/download/:photoId) only wrote to access_logs — the most common download path never reached the bell. Now emits gallery_downloaded with scope 'single', debounced to one notification per event per hour: a guest saving 30 photos is one signal, not thirty (exact counts stay in access_logs/analytics). - getNotificationStyle's icon names were dead — AdminHeader hard-coded <Bell> for every row. Added an icon map so gallery opens (Eye), downloads (Download), favorites (Heart) and the pre-existing style names render their intended icons. * fix(notifications): notify after successful delivery, complete the icon map (codex review of #849, round 2) - Single-photo notification now fires on res 'finish' with status < 400: emitting up-front logged downloads that then 404ed/failed AND burned the 1h debounce window against the next real download. - Icon map completed over every name getNotificationStyle returns (grep-verified) — settings/user/mail/etc. styles render their declared icons instead of falling back to Bell. Deliberately NOT taken from the review: DB-backed debounce state for multi-worker deployments. The backend's current deployment contract is single-process (no PM2 cluster in-repo; multi-replica explicitly parked in #799 — chunked-upload/session state is process-local for the same reason). Worst case under a future multi-worker setup is N notifications per window, which degrades, not breaks; a shared-store debounce belongs to the #799 phase-3 work. * fix(notifications): attribute client sessions, log cached-ZIP after finish, add Trash2 icon (codex review of #849, round 3) - gallery_opened/gallery_downloaded now carry the real actor: client sessions (accessLevel 'client') are recorded as 'customer' instead of being mislabeled 'guest' — #746 explicitly covers client activity, so they are attributed, not excluded. - Cached-ZIP streaming path logs on res 'finish' (< 400) like the single-photo path — piping is not delivery. The presigned-redirect and on-the-fly-archiver paths keep their existing timing (redirect handoff / post-finalize). - Trash2 added to the icon map (customer_erased, bulk_delete_completed no longer fall back to Bell — the grep that built the map missed the digit in the name). * fix(notifications): dashboard formatting, portal dedup, actor-aware wording, archiver finish-hooks (codex review of #849, confirmation round) - activity_logs feed TWO surfaces: the dashboard's Recent Activity used admin.activities.<type> keys that didn't exist, rendering raw identifiers — added gallery_opened/gallery_downloaded entries in all 8 locales. - Customer-portal opens already log customer_event_access at the access-token mint; the ensuing /photos call no longer double-notifies (client sessions surface via downloads only). - gallery_downloaded formatting is actor-aware: customer sessions render 'Customer downloaded…' (new galleryDownloadedCustomer key ×8) instead of 'A guest…'. - Both on-the-fly ZIP paths (download-all fallback + download-selected) notify on res 'finish' < 400 — archive.finalize() ends Archiver's input, not the HTTP transfer. * fix(notifications): key customer dedup/attribution on portal provenance, neutral favorite wording (codex review of #849, final round) The previous dedup was inverted: portal-minted tokens carry via:'customer' but NO accessLevel (they run as guest), while PIN-client logins carry accessLevel:'client' and log nothing else. So PIN clients' only open signal was suppressed while portal opens still double- notified and portal downloads read as guest activity. verifyGalleryAccess now surfaces req.viaCustomer; gallery_opened dedups on THAT (portal only), and galleryActor treats via-customer OR accessLevel-client as 'customer'. photoFavorite wording is actor-neutral across all 8 locales — feedbackService logs favorites without an actor, so claiming 'a guest' was wrong for customer favorites. |
||
|
|
e8dad4b40d |
feat(slideshow): guest-scannable share-link QR overlay (#848)
* feat(slideshow): guest-scannable share-link QR overlay (#837) - Global settings (Settings → Slideshow): slideshow_qr_enabled/position/ opacity/size — same option shape and cascade as the watermark. - Per-event tri-state show_qr (migration 163): NULL inherits the global, true/false force on/off; editable in the per-event slideshow card. - State endpoint ships the QR as a PNG data URI (cached per share URL — the 3s projector poll never re-encodes), so the kiosk needs no QR lib and no extra authenticated request. - Kiosk renders the QR in a white padded corner box so it stays scannable on any photo. - i18n: en + de (the slideshow namespace has no other locales yet). * fix(slideshow): persist per-event QR override, show QR on empty shows, bound the QR cache (codex review of #848) - OverviewTab never passed event.show_qr into the settings card (and the Event type lacked the field), so a stored true/false override always displayed as 'inherit' and the next save silently reset it to NULL. - The QR overlay was nested inside the photos.length > 0 branch — an empty or category-filtered live gallery showed only 'Waiting for photos', exactly when 'scan to add the first photos' matters most. Now rendered for any running show. - slideshowQrCache: insertion-order eviction at 50 entries — rotated tokens and past events no longer accumulate base64 PNGs forever. * fix(slideshow): derive the QR origin from the kiosk request when the base is loopback (codex review of #848, round 2) With the compose-default FRONTEND_URL=http://localhost:3000 (or no base configured) the overlay QR sent scanning phones to their own localhost. The state poll comes from the kiosk browser itself, so its Host header + protocol (trust proxy is configured) are exactly the public origin guests can reach — used whenever the configured base is missing or loopback. Mirrors the ?origin= fallback #847 uses for the admin-side QR downloads. * fix(slideshow): kiosk passes its origin for the QR fallback (codex review of #848, round 3) req.get('host') is not the browser origin behind the standard proxies — frontend/nginx.conf forwards $host with the port stripped, so a compose LAN deployment on :3000 encoded port 80. The kiosk now sends window.location.origin with the session/state calls (validated server-side, same pattern as #847's admin downloads); the Host-derived origin remains as second fallback. * fix(slideshow): reject loopback kiosk origins, throttle QR regeneration per event (codex review of #848, confirmation round) - A loopback window.location.origin from the kiosk is no more guest-reachable than the loopback base it would replace — rejected; when no reachable URL remains the overlay is suppressed entirely (no QR beats a QR that sends phones to their own localhost). New test pins the suppression. - The QR cache is keyed by event id with a 60s regeneration throttle: the origin is caller-influenced when the base is loopback, so URL-keyed caching let a slideshow-link holder force a fresh QRCode.toDataURL per request via unique origins — a cheap CPU exhaustion path. Encode rate is now bounded per event regardless of input. QR margin also raised to the 4-module spec quiet zone, matching #847. * fix(slideshow): never serve a mismatched cached QR + single-flight encoding (codex review of #848, final round) - A slideshow-token holder could poison the projector's QR: an attacker-origin entry cached per event was served to the legitimate kiosk for the rest of the throttle window. A cached artifact is now only served when its URL matches the request; mismatches inside the window suppress the overlay briefly instead of showing foreign content. - Cold-cache stampede closed: concurrent polls share one in-flight encode promise instead of each scheduling a 512px render. Rejected from the same round (false positive, verified empirically): the loopback regex claim — /^https?:\/\/(localhost|127\.)/ matches 'http://localhost:3000' and '127.0.0.1:port' just fine (no trailing slash required), and the suppression test runs green. |
||
|
|
60cdd07085 |
feat(events): gallery QR code + printable table-card/poster PDFs (#847)
* feat(events): gallery QR code + printable table-card/poster PDFs (#836) - GET /api/admin/events/:id/qr — share-link QR as PNG (128-2048px) or SVG, inline or attachment; adminAuth + events.view + ownership. - GET /api/admin/events/:id/qr-print — pdfkit-rendered A6 table card / A4 poster with event name, QR, localized caption (8 locales; Cyrillic falls back to English — built-in Helvetica has no Cyrillic glyphs) and the share URL as footer. - Event detail: QR section in ShareLinkCard with live preview (blob fetch — Bearer auth) and PNG/SVG/table-card/poster downloads; print language follows the admin UI language. i18n keys in all 8 locales. - qrcode + pdfkit were already dependencies (MFA / CRM PDFs). * fix(events): QR origin fallback, Unicode PDF font, bounded layout, stale-preview guard (codex review of #847) - QR URLs: prefer the configured public base, but fall back to the admin browser's origin (passed as ?origin=, validated) when the base is missing or localhost — mirrors buildShareLinkUrl so the QR encodes the same URL the card displays instead of an unusable localhost target. - PDFs render with the bundled IBM Plex Sans TTFs (Latin+Cyrillic+Greek) instead of WinAnsi-only Helvetica: Cyrillic event names no longer silently disappear, and the caption's English-fallback hack is gone. - Fixed vertical layout: title gets a bounded two-line ellipsis region and all positions derive from constants, so long event names can't push the QR/caption over the footer; URL footer bounded too. - ShareLinkCard preview: stale-response guard — a late blob response after unmount/event-switch is revoked instead of leaking and overwriting the newer event's QR. * fix(events): bundle complete IBM Plex Sans for QR PDFs + IPv6 loopback fallback (codex review of #847, round 2) Round 2 caught that the pre-existing assets/fonts/IBM-Plex-Sans/ files are 270-glyph Latin SUBSETS — my round-1 font swap didn't actually fix Cyrillic titles and regressed the ru caption. Now bundling the complete IBM Plex Sans 400/700 TTFs (1019 glyphs, Latin+Cyrillic+Greek — cmap verified via fontkit, rendering verified on a generated PDF) under assets/fonts/IBM-Plex-Sans-Full/ with the OFL license alongside. ~400 KB total; source: IBM/plex release zip @ibm/plex-sans@1.1.0. Also: LOCAL_BASE_RE now recognizes IPv6 loopback ([::1]) so a FRONTEND_URL of http://[::1]:3000 falls back to the browser origin like the frontend's own URL logic does. Note for a follow-up: the CRM invoice/quote PDFs use the same Latin-only subsets and share the Cyrillic gap. * fix(events): responsive QR card that survives preview failures (codex review of #847, round 3) - The QR section keys off share-link availability instead of a loaded preview: a transient failure of the preview request no longer hides every download button until reload; a placeholder tile renders in place of the image. - Preview + actions stack on phone widths and the button grid drops to one column below sm, so 'Tischkarte (A6)'-length labels don't overflow. * fix(events): QR encodes the stored share_link + spec quiet zone (codex review of #847, confirmation round) - The QR target is now the STORED share_link — exactly what the card displays and the admin copies. Rebuilding from current slug/token/ short-URL setting could diverge for legacy absolute links or events created under a different short-URL setting; a printed QR encoding a different URL than the card is a permanent mistake. Rebuild remains only as fallback when no share_link is stored. - QR margin back to the library's 4-module default for all generated assets — the spec's quiet zone; margin 2 risks scan failures when the printout sits against colored surroundings. * fix(events): bare share_link tokens resolve as /gallery/<token> in QR URLs (codex review of #847, final round) Quote-/contract-converted events persist share_link as the raw token — the frontend's buildShareLinkUrl prefixes those with /gallery/, but the QR path normalization only added a leading slash, encoding <origin>/<token> into every image/PDF for such events. Now mirrors the frontend exactly. * test(events): 30s timeout for the print-PDF cases (CI fix) The poster PDF now embeds the full IBM Plex Sans TTFs (~200 KB each); font parsing + subsetting exceeds jest's 5s default on slower CI runners — the suite went red on exactly that test after the font commit. |
||
|
|
613133c29c |
chore(main): release 3.92.2-beta.0 (#852)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
f891b16503 |
chore(main): release 3.92.1-beta.0 (#842)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
14d5fa6ca5 |
chore(main): release 3.92.0-beta.0 (#840)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
8c260c4eeb |
Merge pull request #833 from PicPeak/feat/guest-upload-dng-raw
feat(uploads): DNG / camera-RAW support via embedded-preview extraction (#821) |
||
|
|
ec69ad84f2 |
chore(main): release 3.91.0-beta.0 (#835)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
d7ba781c0f |
Merge remote-tracking branch 'origin/main' into feat/guest-upload-dng-raw
# Conflicts: # backend/src/services/uploadSettings.js # backend/src/utils/fileSecurityUtils.js # frontend/src/utils/fileTypes.ts |
||
|
|
ee9d2f70d3 |
Merge pull request #832 from PicPeak/feat/guest-upload-heic-dynamic-hint
feat(uploads): HEIC/HEIF support + dynamic format hint on guest upload (#821) |
||
|
|
c9b64d9c1a |
fix(uploads): register HEIC/HEIF with the file validator + fix admin format hint (codex review of #832)
Two findings from the Codex review:
- validateFileType requires an ALLOWED_MEDIA_TYPES entry, which had neither
image/heic nor image/heif — so HEIC was rejected before sharp ever saw it,
despite the EXTENSION_TO_MIME additions. Added both with a single 'ftyp'
(offset 4) magic number (the check is .every, so alternatives can't be
separate entries).
- Changing the shared upload.fileRequirements string to interpolate {{formats}}
left the admin PhotoUpload caller passing only { limit }, rendering the
placeholder literally (it was also already dropping {{sizeLimit}} from #823).
The admin caller now passes formats + sizeLimit + limit, from the admin
settings it already loads.
|
||
|
|
be2ec0a4a1 |
feat(uploads): DNG / camera-RAW support via embedded-preview extraction (#821)
Sharp's bundled libvips has no raw loader, so a DNG can't be thumbnailed directly. This adds a preview-extraction step so RAW/DNG uploads get a proper thumbnail + gallery preview while the original RAW is kept for download. - imageProcessor: isRawFilename() + extractRawPreview() (exiftool extracts the embedded full-res JPEG — JpgFromRaw → PreviewImage → ThumbnailImage, validated with sharp) + withProcessableImage() which is a pass-through for ordinary images and swaps in the extracted JPEG for RAW. Wired into ingest (photoProcessor) and all three on-demand generators (ensureThumbnail/Hero/ Preview). generateHeroImage/generatePreviewImage gained outputBasename so RAW-derived outputs stay named after the source. - Dockerfile: add exiftool (confirmed present in Alpine v3.24 community). - Format maps: dng → image/x-adobe-dng in uploadSettings.js and fileTypes.ts; ALLOWED_MEDIA_TYPES gains a DNG entry (TIFF magic numbers) so it passes the security file-validator. Strictly gated by extension: nothing in this path runs for jpg/png/webp/etc, so existing photos are unaffected. If extraction fails (corrupt RAW, no embedded preview), the photo is marked 'failed' with a clear error — same as any unreadable upload. Verification boundary (please validate on a real DNG after the image rebuilds): the exiftool extraction itself couldn't be exercised in the dev sandbox (exiftool isn't a dev dependency and there's no DNG fixture). Unit tests cover the gating (RAW detection + non-RAW pass-through + clean failure without exiftool); existing processPhoto tests still pass. Known limitation: a DNG is only accepted when the browser reports its MIME as image/x-adobe-dng (Chrome does); browsers that send an empty type reject it client- and server-side — a follow-up can add extension-based acceptance for the RAW set. Companion to the HEIC/dynamic-hint PR; targets main only. |
||
|
|
8e0005e170 |
chore(main): release 3.90.2-beta.0 (#826)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
2b5b23b96f |
feat(uploads): HEIC/HEIF support + dynamic format hint on guest upload (#821)
Two of the three things from #821: - HEIC/HEIF (iPhone) can now be enabled. Sharp's bundled libvips decodes `heif` input (verified: sharp.format.heif.input.file === true on 0.34.3 / libvips 8.17.1), so thumbnails generate. Added heic/heif to EXTENSION_TO_MIME in both the backend (uploadSettings.js) and the frontend (fileTypes.ts) maps, which are kept in sync. (iOS Safari usually transcodes HEIC→JPEG at file selection, but a genuine .heic upload is now handled when it arrives.) - The upload requirements hint no longer hardcodes "JPEG, PNG or WebP". New extensionsToLabel() renders the actually-configured, supported formats (e.g. "JPG, PNG, WEBP, MOV"), and upload.fileRequirements interpolates {{formats}} across all 8 locales. Unsupported extensions are dropped from the label so it never advertises a format the backend would reject. DNG / camera RAW is deliberately NOT included: Sharp's libvips has no raw loader, so a DNG would upload then fail thumbnailing (photo → 'failed', no preview). Proper RAW support (embedded-preview extraction) is a separate PR. Adds vitest coverage for extensionsToLabel + the HEIC mapping. |
||
|
|
e03d13efde |
fix(uploads): tighten guest max-file-size setting (codex review of #823)
Three follow-ups from the Codex review of #823: 1. PublicSettings TypeScript interface was missing general_max_file_size_mb, so UserPhotoUpload's access produced TS2339 under `tsc -b` (build:check). CI didn't catch it because the pipeline runs `build` (esbuild, no typecheck), but it's a real type gap — the #614 count field is declared, this one wasn't. Added the optional numeric field. 2. The general-settings update endpoint validated general_max_files_per_upload but not general_max_file_size_mb, so an out-of-range value (0, -1, huge) could persist. publicSettings then advertised the raw value while getMaxFileSizeMb() normalised it — the guest UI would reject files the backend accepts. Added the same validate-and-clamp block (1..MAX_ALLOWED_FILE_SIZE_MB). 3. The update route cleared the file-count cache but not the new file-size cache, so for up to 60s the public endpoint could advertise a new limit while multer still enforced the old one. Now clears both under the same uploadLimitTouched guard. Follow-up on the merged #823 (main-only), so this targets main only. |
||
|
|
1e38d84808 |
fix(uploads): apply configured max file size to guest uploads (#613 follow-up)
The admin's Settings → General → "Max File Size (MB)" value (general_max_file_size_mb) never applied to guest gallery uploads — the guest route hardcoded multer's per-file cap at 50MB (gallery.js) and the guest UI hardcoded the same 50MB client-side guard and "max 50MB" hint text. So a guest could not upload a large video even when the admin raised the limit (reported by mat1990dj on #613). Same class as the file-count miss fixed in #614, for size. - uploadSettings.js: new getMaxFileSizeMb()/getMaxFileSizeBytes() reading general_max_file_size_mb (default 50MB, cached 60s, clamped to a 10GB ceiling), mirroring getMaxFilesPerUpload. - gallery.js (guest upload): multer limits.fileSize now resolves from the setting; a LIMIT_FILE_SIZE error returns an actionable "max N MB" message. - publicSettings.js: exposes general_max_file_size_mb (default 50) so the gallery UI can render the real limit and guard client-side before an oversized POST. - UserPhotoUpload.tsx: reads the limit, uses it for the client-side size guard, and passes it to the requirements hint. The "max 50MB" literal in upload.fileRequirements is now interpolated ({{sizeLimit}}) across all 8 locales; adds upload.fileTooLarge (en/de; others fall back to en). Scope: guest path only (the reported gap). The admin path keeps its generous 10GB cap — admins are trusted and default 50MB would otherwise regress large admin video uploads. Format and batch-size limits already work correctly and are untouched. Adds SQLite-backed unit tests for the new getter. Verified end-to-end on a booted instance: admin sets 500MB → persisted → public settings exposes 500 → guest multer sources its cap from it. |
||
|
|
1b32d4691e |
chore(main): release 3.90.1-beta.0 (#820)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
6cd546e86a |
fix(security): remove unguarded legacy /api/events router (GHSA-4j34-x562-5vfq)
The legacy gallery router mounted at /api/events exposed create/list/update/
delete/extend guarded by adminAuth ALONE — no requirePermission, no
requireEventOwnership. adminAuth only checks the token is a valid type:'admin'
session, which every back-office role holds, down to read-only `viewer`. So any
non-super-admin account could:
- GET /api/events → every gallery's bcrypt password_hash, share_token, and
client name/email (the list handler selects * and mapEventForApi keeps
those columns),
- PUT /api/events/:id → reset any gallery's password (full takeover),
- DELETE /api/events/:id → delete any gallery,
all bypassing the per-photographer ownership isolation the canonical
/api/admin/events router enforces. Affects any instance with more than the
single super_admin.
Fix: remove the legacy router entirely (mount + require + src/routes/events.js).
It was a superseded duplicate of /api/admin/events and unused by the frontend
EXCEPT for one live route — POST /:id/extend (the "Extend expiration" UI action,
which hit /api/events/:id/extend via the api client's /api base). That route is
migrated to the canonical mount as POST /api/admin/events/:id/extend with the
same guards as every other gallery mutation (adminAuth + requirePermission
('events.edit') + requireEventOwnership), and the frontend is repointed to it.
Behaviour of the extend itself is unchanged (expires_at + reactivate).
Verified end-to-end on a booted instance: /api/events (all methods) now 404;
/api/admin/events/:id/extend returns 401 unauth, 200 for the owner, 403 for a
non-owning editor; the full login→create→extend flow works. Adds a regression
test pinning the router removal and the extend ownership check.
|
||
|
|
7f22a9ee3d |
chore(main): release 3.90.0-beta.0 (#816)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
cbde7636aa |
Merge remote-tracking branch 'origin/main' into feat/oidc-sso-phase1
# Conflicts: # backend/src/middleware/maintenance.js |
||
|
|
b5ac24ea46 |
chore(main): release 3.89.0-beta.0 (#814)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
7ebc232620 |
Merge pull request #811 from PicPeak/fix/security-advisories-backend
fix(security): close 4 open security advisories (backup takeover, share-login bypass, ZIP slip, chunked-upload traversal) |
||
|
|
199dab82ae |
Merge pull request #808 from PicPeak/fix/docker-image-os-cves
chore(security): close 21 frontend image CVEs — nginx 1.30 base + apk cache-bust |
||
|
|
38fd41aad3 |
fix(security): harden .picpeak restore operator-preservation (GHSA-qxfx follow-up)
The req.admin.id fix activated reinjectCurrentAdmin(); hardening its preservation logic (found across Codex review rounds of #811): - MFA hijack: reinject wrote back only password_hash/is_active/ must_change_password, leaving a crafted backup's two_factor_* on the operator's row — it could strip or replace their second factor. The email- matched row is now updated with the operator's full AUTH set (login identity, password, and all two_factor_* columns). Relationship/audit FKs (role_id, created_by) are deliberately NOT forced from the snapshot: on a cross-instance restore those pre-restore ids may be absent from the backup and would dangle the FK (SQLite rolls back at commit); the restored row keeps its own valid values. - Cross-instance restore rollback / FK safety: reinject matched only by email, so a backup shipping a different admin with the default `admin` username hit UNIQUE(username) and rolled the whole restore back; email and username could even collide on two different rows. Reconciliation is now non-destructive: the email-matching row is updated in place (id preserved → restored FKs like events.created_by stay valid); any different row holding the operator's username is RENAMED, not deleted (deletion would fire ON DELETE actions / dangle references); only when no row has the operator's email is a fresh row inserted, with created_by nulled and an explicit max(id)+1 id (batchInsert left the Postgres identity sequence unadvanced, so a sequence-based insert could collide). - Stale session after restore: admin_users ids shift on restore, but the operator's live JWT is bound only to decoded.id (IP logged not enforced; the backup controls password_changed_at). The route now revokes the token (result checked and logged) and clears the admin cookie; the client redirects to a fresh login via a sessionInvalidated flag. Cookie clear is the unconditional guarantee. Adds SQLite-backed reinject regression tests (in-place login/MFA restore with id and FK columns preserved, username-only rename, email+username on different rows, clean insert with created_by nulled) and the frontend redirect on sessionInvalidated. Deferred (design decisions / pre-existing, need a Postgres test env — see PR discussion): global "invalidate all pre-restore sessions" cutoff; preserving the operator's ROLE semantics across an RBAC-table replace; and resyncing Postgres identity sequences after any restore (batchInsert leaves them behind max(id) — pre-existing, affects every restored table). |
||
|
|
efccecb3d8 |
chore(main): release 3.88.1-beta.0 (#810)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
|
||
|
|
eb03b61268 |
chore(security): close 21 frontend image CVEs — nginx 1.30 base + apk cache-bust
The frontend image kept shipping vulnerable OS packages (nginx 1.28.3-r1, curl/libcurl 8.19.0, c-ares 1.34.6) despite the apk upgrade line, for two independent reasons: 1. The runtime stage's apk upgrade layer was cached indefinitely — the CACHEBUST build-arg CI passes (github.run_number) was only declared in the builder stage, and ARGs don't cross stage boundaries. Both Dockerfiles now redeclare CACHEBUST in the runtime stage and consume it in the apk RUN, so every build re-runs the upgrade and picks up current Alpine security updates. 2. nginx itself can never upgrade via apk on the nginx.org-based image: the bundled nginx-module-* packages pin the exact nginx version, so Alpine's patched 1.28.3-r4 is unreachable (verified empirically — apk add --upgrade nginx is a silent no-op). nginx fixes must come via the base tag, so bump to nginx:1.30-alpine (current stable, 1.30.4 on Alpine 3.24, same nginx.org conf.d layout — drop-in). Verified: local image build scans clean with Trivy (0 OS findings, was 21); container serves /health, SPA fallback, and BRAND_TITLE envsubst as non-root nginx user. Closes code-scanning alerts 371-374, 376-392 (nginx HTTP/2 & module CVEs, curl CVE-2026-5773/-6276 + 6 medium, c-ares CVE-2026-33630). |
||
|
|
e91c7deaa4 |
fix(oidc): local-credential lockout, session hydration, split-origin gaps (codex round 3)
- OIDC-owned accounts can never authenticate locally: the password login rejects auth_provider='oidc' rows outright (generic 401), and the super-admin password reset refuses them with a clear message — previously a reset would have minted a local password bypassing the IdP's MFA/access policies - /auth/session now returns a full adminUser payload (role join) and AdminAuthContext hydrates user state from it: an SSO redirect establishes the session without any login JSON, which left the header identity blank and current-admin form defaults empty - the /sso/login error path redirects absolute to the frontend base (same split-origin reasoning as the callback) - docker-compose.yml passes API_URL through to the backend (production compose uses env_file and needs nothing; dev compose is gitignored) - authSession.symmetry test mock taught the joined admin lookup (leftJoin, prefixed columns, aliases) — the route change made the old mock throw, which read as "table missing, trust token" Tests: new case pins that a known-good password on an OIDC-owned row still gets 401. 14/14 OIDC, 13/13 symmetry. |