Commit graph

1117 commits

Author SHA1 Message Date
Keith Martin
b6687b6c29
Tests: Improve isolation and add fixes #5263
* Tests: fix random selection of deleted record causing test failure
* Tests: fix test folders being left after test
* Tests: ensure that required records are available
* Tests: make each package execute against it's own testdata folder to ensure no interpackage test failures
* Tests: add unit tests of CleanupTestFolder
* Tests:  Allow defer in TestMain
* Tests: rename testMain to runTestMain
2026-07-12 05:47:21 +02:00
Ömer Duran
6dc0acac13
Settings: Reorganize UI and add an Accessibility section #848 #799 #5429
* Frontend: Add settings for albums, favorites, folders, and media features

* Frontend: Enhance download settings for files and albums #848

* Frontend: Enhance download settings for files and albums #848

* Frontend: Update album download settings to restrict access to super admins

* Frontend: Add album sorting options to settings for super admins

* Frontend: Update download settings visibility

* Frontend: Refine download settings access control for user roles

* Tests: Add album, folder, moment, state, and month order selection options to page-model

* Frontend: Update access control for super admins in settings to include scope check

* Tests: Add new tests for download name and album order settings persistence

* Tests: Implement new tests for default album sort order persistence in settings

* Frontend: Move default album order settings

* Frontend: Add zoom accessibility option in settings #799

* Frontend: Update download name from "Share Identifier" to "Share Friendly" and refine hints for download options in settings

* Tests: Add new acceptance test for disabling album downloads while allowing file downloads in settings

* Frontend: Improve download options layout and hints

* Frontend: Refactor download options condition in settings page

* Frontend: Implement native zoom control in lightbox component

* Settings: Add Collections tab and move Services to its own page #848 #5429

- Add a super-admin Collections tab after Content holding the album
  Download and Sort Order settings; remove those cards from Content.
- Move Services from a settings tab to a dedicated page
  (page/services.vue) with a search toolbar and a Reload / Learn More
  action menu; link it from the navigation settings menu.
- Implement server-side services search (acc_name filter) with tests.
- De-duplicate the picture sort-order options into a shared
  SortOrderOptions builder in options.js.
- Update acceptance/unit tests and page models to match.

* Frontend: Regenerate translation catalogs #848 #5429

Ran `make gettext-extract` to pick up the new settings strings
(Collections, Features, Disable Downloads, and related messages) so
Weblate can translate them. Existing translations are preserved.

* Frontend: Move lightbox viewport-zoom handling into the view helper

- Move the viewport pinch-zoom lock/restore (disableNativeZoom /
  restoreNativeZoom + savedViewportContent) from lightbox.vue onto the
  View singleton in common/view.js.
- Drive it passively from View.apply() via a disableViewportZoom flag in
  the PLightbox case, alongside hideScrollbar / disableScrolling /
  disableNavigationGestures; lightbox.vue no longer references zoom.
- Make save/restore idempotent and extract the viewport constants.
- Cover the helpers and the apply() wiring in view.test.js.

* Settings: Add Accessibility section to General with Reduce Motion #848 #5429

- Move the page-zoom checkbox out of the feature-flags grid into a
  dedicated Accessibility card (super-admin only) with four toggles:
  Open on Hover, Reduce Motion, Hide Scrollbar, and Allow Page Zoom.
- Add a UI.ReduceMotion flag (defaults false) that toggles an
  html.reduce-motion class via config.setReduceMotion and suppresses map
  fly-to animations non-destructively (util.mapAnimateDuration).
- Make Open on Hover apply live by reading shouldOpenOnHover() through a
  reactive computed in the action/auth/lightbox menus; Zoom and Hide
  Scrollbar reload since they are baked into the server-rendered HTML.
- Reword two feature-grid hints to the "pictures" convention and cover
  the new helpers with Go and Vitest tests.

* Frontend: Regenerate translation catalogs #848 #5429

Ran `make gettext-extract` to pick up the new Accessibility settings
strings (Accessibility, Allow Page Zoom, Open on Hover, Hide Scrollbar,
Reduce Motion, and their hints) and the reworded feature-grid hints.
Existing translations are preserved.

---------

Co-authored-by: Michael Mayer <michael@photoprism.app>
2026-07-08 14:11:36 +02:00
Michael Mayer
d330f0e05c Users: Apply 2FA changes for the cluster service principal 2026-06-29 21:59:48 +00:00
Michael Mayer
a2e8592dd5 Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
Ömer Duran
b2b1785126
Tests: Add unit tests for recent metadata, auth, and config changes #5697 2026-06-26 14:12:59 +02:00
Michael Mayer
41fdb578b2 Users: Prevent a non-super-admin admin from locking out the super admin 2026-06-25 16:24:51 +00:00
Michael Mayer
11c074cb85 Auth: Delegate cluster OIDC logout to the Portal end-session endpoint #5684
On a cluster-OIDC Sign-Out the instance now delegates the Portal session and the
upstream RP-logout to the Portal's end-session endpoint instead of doing them
itself: revokePeerSessions skips the Portal peer's backend DELETE (still clearing
its storage), and DeleteSession skips clearing the OP session cookie when it
returns a providerLogoutUri — otherwise the Portal OP could not resolve the
session (via that cookie) to chain the upstream logout. onLogout now resolves to
the chosen landing URL so the /logout route guard follows the provider logout URL
too. Adds the oidcLogout client-config accessor and an instance.portal session
marker so the Portal peer is identified explicitly rather than by URL shape.
2026-06-24 15:22:04 +00:00
Michael Mayer
50976f427e Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433
Sign-out of an OIDC session redirects the browser to the provider's
discovered end_session_endpoint (id_token_hint + absolute
post_logout_redirect_uri) when PHOTOPRISM_OIDC_LOGOUT is enabled, so the
provider SSO session ends and the next login re-prompts. Opt-in (default
off); local/LDAP logout and providers without an end-session endpoint fall
back to local logout.

Adds the shared CE /api/v1/oauth/logout route and OAuthLogoutHandler hook
plus the Portal OP end_session_endpoint advertised in the Portal discovery
document, for Portal builds to serve via the override hook.
2026-06-24 17:21:00 +02:00
Cathie Integra
3f096a592b Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
Michael Mayer
c5658a053d API: Align i18n.Response Go field names with JSON keys #5682
Rename the Response struct fields to match their json tags (Err to Error, Msg to Message) and the Error() method to ErrorString() to avoid a field/method clash; update callers and tests. JSON wire format is unchanged.
2026-06-23 12:42:57 +00:00
Michael Mayer
fd8448d6de UX: Name message fields messageId/messageParams in i18n payloads #5682
Rename the i18n notification/error envelope fields from id/params to messageId/messageParams across the WS notify Data and the i18n.Response body, so they are unambiguous next to session, log, and correlation ids. Updates the frontend consumers (notify.vue, api.js), tests, swagger, and the event/locales READMEs.
2026-06-23 12:22:30 +00:00
Michael Mayer
f5373ccec6 UX: Render backend error responses in the current UI locale #5682
The i18n.Response envelope now carries the untranslated source id and params; the frontend (api.js) renders them via Tp in the user's UI locale, with the server-rendered error/message as the instance-locale fallback. Common not-found/conflict/bad-request handlers were switched from hardcoded literals and raw err.Error() to Abort/i18n.Message helpers, so they benefit automatically.
2026-06-23 12:06:49 +00:00
Michael Mayer
9ec70731e2 UX: Fix notification messages that bypassed translation #5682
Route user-facing notifications through extractable $gettext (aliased ctx.$gettext is not extracted), wrap hardcoded English strings, replace concatenation with interpolation, and drop redundant English backend label toasts already covered by localized frontend ones.
2026-06-23 09:06:56 +00:00
Michael Mayer
43506ad883 Thumbs: Add 16K (15360px) fit size for photos and videos #5669 #5623 2026-06-17 04:12:40 +00:00
Ömer Duran
3ea547a564
Lightbox: Add support for 360° photos and videos #352 #4718 #5623
* Lightbox: Add 360° photo and video sphere viewer support

Wires Photo Sphere Viewer v5 into the lightbox for equirectangular
media. Backend detects UsePanoramaViewer and IsPhotosphere markers
alongside ProjectionType, and exposes Panorama + Projection in the
viewer / search result DTOs so the frontend can dispatch on the
projection type. The renderer (ThreeJS + PSV core + video plugin +
equirectangular video adapter) is lazy-loaded into a dedicated
webpack chunk so the base bundle is unchanged for users with no
360° media. PhotoPrism's existing lightbox controls drive the
underlying HTMLVideoElement that the PSV adapter creates for 360°
videos. Includes Vitest coverage for the sphere helper and the
lightbox dispatch, plus a TestCafe smoke spec and three new
ExifTool metadata fixtures.

* Lightbox: Fix 360° controls leak, hide zoom button, add sidebar icon

* Lightbox: Fix 360° touch panning and navigation arrows

* Lightbox: Fix fast swipe switching photos on 360° slides

Suppress PhotoSwipe swipe/drag navigation at its source via the dispatched pointer hook while a sphere slide is active, since the per-element gesture trap is outrun by a fast swipe whose pointer leaves the sphere container (touch-capable Windows). UI controls stay excluded so buttons and arrows still navigate.

* Lightbox: Route only equirectangular media to the 360° viewer

Routing was based on the loose photo_panorama flag for videos, which is also
true for cubemap and merely-wide (aspect > 1.9) videos, so non-equirectangular
media wrongly opened, distorted, in the sphere viewer. Route strictly on the
equirectangular projection for both photos and videos. Surface the video file's
projection in the viewer DTO via Photo.MediaProjection() (the primary search row
is a poster JPEG with no projection). Add an MP4 metadata-detection test.

* Lightbox: Open tagless 2:1 360° videos in the sphere viewer

Many 360° videos carry no projection tag PhotoPrism can read, so strict
equirectangular-projection routing left them in the flat player. Fall back to
equirectangular's defining 2:1 frame for panorama-flagged videos without a
projection, via is360Equirectangular() in common/sphere.js, while cubemap and
ultrawide (~2.35:1) clips stay flat.

* Tests: Make the 360° sphere video test assert instead of skipping
2026-06-17 03:26:25 +02:00
Michael Mayer
75a5e1ee0b Auth: Log anonymous API requests at debug level instead of warning #5647
Only warn when a request presents a token that is rejected; requests
without any credentials (e.g. the web app loading public config before
login) are routine and now logged at debug level, reducing log noise.
2026-06-15 23:56:56 +00:00
Michael Mayer
c45ee6e54f Auth: Revoke derived sessions & gate app passwords by login state #5647
- Add scoped session revocation (RevokeSessions / RevokeDerivedSessions)
  shared by the user-update API and the CLI: a privilege-level change now
  revokes logins and app-password-derived sessions while keeping the app
  passwords themselves, so configured devices keep working.
- Identify app-password-derived sessions by auth_method "session".
- Deny app passwords on the REST API when the account cannot log in
  (DenyLogIn); WebDAV access stays governed by CanUseWebDAV.
- Reduce the WebDAV auth cache expiration to one minute.
- Add IsSystemOrInvalid; allow deleting the initial admin account and skip
  re-initializing a deactivated or deleted admin in InitAccount.
- Move session-revocation scopes to pkg/authn and extend tests.
2026-06-15 23:41:01 +00:00
Michael Mayer
997ff2e70d People: Load name suggestions from typeahead cache #5666
Move the people name list out of the client config into the shared
typeahead cache (getPeople, backed by GET /api/v1/subjects), matching
labels and albums. Consumers load suggestions on demand and the cache
evicts on people.* / subjects.* events and clears on logout; the
config.js people machinery is removed and ClientConfig keeps only
count.people for the navigation badge.
2026-06-15 14:38:04 +00:00
Michael Mayer
6addeaa7a4 People: Scope client config people list to authorized roles #5666
Groundwork for the people type-ahead cache: include the people list and
count in the client config only for roles that may view or search people,
and omit the list from the global config.updated broadcast. Clients load
it via GET /config and keep it current through people.* events.
2026-06-15 13:45:38 +00:00
Michael Mayer
bc327016ad Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656
Mirror the lens make/model editing surface for cameras: entity UpdateMakeModel/SaveForm, form validation, query, search, the GET/PUT /api/v1/cameras endpoints, and the cameras CLI command, plus a cameras ACL resource and scope.

Also tidy the lens surface for parity: self-validating SaveForm, empty make/model guard, X-Count search header, service-role grant, the empty-id/slug docs, and order cameras before lenses everywhere.
2026-06-15 09:25:44 +00:00
Keith Martin
7c6546ce19
API: Search X-Count header for Labels and Services #5649
* API: Add X-Count response headers for services and labels.
* API: update swagger annotations for response headers
2026-06-15 10:00:08 +02:00
Keith Martin
c3eda657fd
Metadata: Add Lens Make and Model updates via CLI & API #5644 #5656
Adds the ability to override a lens's Make/Model (e.g. fixing Pentax lenses that ExifTool decodes as `4 38`) via a new photoprism lenses update CLI command and a `PUT /api/v1/lenses/:id endpoint`, plus a `GET /api/v1/lenses` search endpoint, a new lenses ACL resource, and an lenses ls list command.
2026-06-15 09:37:32 +02:00
Michael Mayer
9605d87731 Tests: Isolate reaction redaction subtest from mutation fixtures 2026-06-14 16:38:34 +00:00
Michael Mayer
c6cab2856d Photos: Limit by-UID label, marker & file edits to the session scope #1307
Single-photo label add/remove/update, marker create/update/clear, file
unstack, orientation, and delete now apply the same shared-scope
visibility predicate as photo search and update, so by-UID and by-hash
edits stay consistent with album and photo updates and never act on
content outside the session's shared view. File-scoped handlers gate on
the resolved file's owning photo. Full-access sessions skip the check
with no database query via PhotoSessionSeesEverything.
2026-06-14 15:37:57 +00:00
Michael Mayer
6ca27c827b Cluster: Add GroupsSrc to the node DTO for group-config provenance 2026-06-14 12:06:28 +00:00
Michael Mayer
e1bf7096c8 Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
Michael Mayer
e832784856 Users: Prevent disabling own super admin status and web login 2026-06-12 20:36:35 +00:00
Michael Mayer
e308fd7b58 Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
Michael Mayer
d594edd5de Cluster: Declare per-node group config via instance env/flags 2026-06-12 18:52:54 +00:00
Michael Mayer
d091958a60 Cluster: Collect OIDC groups at login for group-based admission 2026-06-12 16:08:38 +00:00
Michael Mayer
7486982342 Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
Michael Mayer
5c6e30260f Photos: Test that in-scope reactions are redacted for shared sessions
Add a guest regression case asserting the like endpoint returns an in-scope picture with identifying metadata stripped, so dropping the per-session redaction call fails a test rather than regressing silently.
2026-06-10 19:24:29 +00:00
Michael Mayer
856600cd02 Photos: Scope photo reactions to the session's shared view
Apply the same per-session visibility gate and response redaction the photo read and update endpoints already use, so the favorite (like/dislike) routes return only what the session is entitled to see. Adds guest regression subtests for both routes. #1307
2026-06-10 18:57:11 +00:00
Michael Mayer
8b09cfe76e Photos: Limit by-UID and batch photo edits to the session scope
Single-photo update/approve/set-primary and batch archive/restore/approve/private/delete now apply the same shared-scope visibility predicate as photo search, so by-UID and bulk edits stay consistent with album updates and never act on content outside the session's shared scope. Visibility is evaluated against both the client and user role for client sessions. Full-access sessions skip the check with no database query via PhotoSessionSeesEverything / SelectedPhotoUIDsForSession.
2026-06-10 19:49:27 +02:00
Michael Mayer
0379692a46 OIDC: Surface account-collision reconcile hint and add --auth-issuer
When an OIDC login matches a pre-existing non-OIDC account, log a
specific operator remedy (the exact 'users mod --auth oidc --auth-id'
command) to the audit and login-error logs instead of a bare provider
mismatch; the user-facing page stays a generic credentials error so the
account is not disclosed. Add a 'users mod --auth-issuer' flag so the
documented manual reconcile can pin the OIDC issuer, scoping the link to
that provider instead of matching any issuer with the same subject.
2026-06-10 15:31:34 +00:00
Michael Mayer
785ae60a9d API: Add SiteName field to the generated swagger.json 2026-06-10 14:28:33 +00:00
Michael Mayer
6cb38abb43 Settings: Add UI OpenOnHover setting for hover menus #5650 2026-06-10 02:48:10 +00:00
Michael Mayer
14edf40abd OIDC: Drop residual cluster-OIDC gate on provisioned account email 2026-06-10 02:13:27 +00:00
Michael Mayer
71aeb26137 OIDC: Store email only when the token marks it verified 2026-06-10 00:04:26 +00:00
Michael Mayer
cb48c209b6 Auth: Gate app passwords behind a settings feature flag #5647
Adds an AppPasswords feature flag (default on) so app passwords can be disabled, closing the bypass where an app password minted by an OIDC-provisioned user keeps working after the account expires. When off, minting (oauth/token and auth add) is rejected and existing app passwords are no longer accepted on the REST API (403) or WebDAV (401). App passwords are identified by their session auth provider (IsApplication), covering every grant type. The Apps and Devices affordance is hidden for sessions that may not change a password; operators can force it off via PHOTOPRISM_DISABLE_FEATURES.
2026-06-09 17:36:24 +00:00
Michael Mayer
7232cc23a4 OIDC: Render a branded page when the RP code exchange fails
The OIDC RP callback handler returned without writing a response when
CodeExchangeUserInfo failed (e.g. a missing/expired state cookie), leaving
the raw zitadel error ("failed to get state: http: named cookie not present")
on screen with no way forward. CodeExchangeUserInfo now runs the exchange
against a recorder so the handler's raw error never reaches the browser, and
the callback renders the branded auth.gohtml page (which returns the user to
login) instead of a dead-end. Set-Cookie headers from a successful exchange
are still propagated.
2026-06-09 13:11:02 +00:00
Michael Mayer
6d0add2e69 Cluster: Address review findings on sign-out, OIDC bootstrap, errors
- /logout route guard now signs out cluster-wide: session.signOut shares
  revokePeerSessions with logoutEverywhere, so a direct /logout revokes peer
  sessions and clears shared storage too, not just the current session.
- Decouple PHOTOPRISM_CLUSTER_OIDC from the auto-join/theme toggles: InitConfig
  now always runs resolveNodeOIDCClient for an instance (join/theme moved into
  bootstrapClusterNode), so a registered node re-wires its OIDC RP on restart
  even with both bootstrap toggles disabled.
- Map redirected OAuth server_error / temporarily_unavailable to ErrUnexpected
  instead of "Invalid credentials", so a Portal-side failure isn't shown as a
  credential problem.
2026-06-09 10:38:22 +00:00
Michael Mayer
b88429f557 Cluster: Compact code comments in the OIDC follow-up changes 2026-06-09 10:20:17 +00:00
Michael Mayer
872af5d8fd Cluster: Add cluster OIDC, branded authorize errors, and sign-out
Implements three OIDC follow-ups from specs/portal/cluster-oidc.md:

- Zero-touch instance OIDC RP config: PHOTOPRISM_CLUSTER_OIDC derives the
  OIDC RP client_id/secret from the node client credentials and defaults
  the issuer to the instance's own origin (scheme.OriginURL), so a single
  boot reaches a working Portal login. Explicit oidc-client/secret/uri win.
- Human-friendly authorize errors: redirectable errors 302 back to the
  validated redirect_uri; non-redirectable ones content-negotiate a
  branded HTML page (oauth-error.gohtml) vs JSON via shared helpers in
  internal/api/oauth_error.go. The RP callback surfaces inbound ?error=.
- Cluster-wide Sign-Out (Tier 2): session.logoutEverywhere fans out a
  best-effort DELETE per peer session, clears their storage, then signs
  out locally; session-delete clears the OP cookie on any shared-domain
  OIDC-against-Portal node (OIDCSessionCookieClearPath).
2026-06-09 09:39:01 +00:00
Michael Mayer
680469e781 Cluster: Add a per-instance node DisplayName field 2026-06-09 07:23:05 +00:00
Michael Mayer
8f7866617a Docs: Document the API JSON field-casing convention
Add the TitleCase (entity-backed) vs camelCase (generated/artificial) rule to the
API package guide and the api-and-config rule's API Shape Checklist.
2026-06-09 03:18:15 +00:00
Ömer Duran
ce5e10fef3
API: Set Cache-Control no-store on the session response 2026-06-05 21:03:38 +02:00
Ömer Duran
3e5522aa80
Auth: Set the OIDC OP session cookie for user sessions only
Skip user-less sessions whose signal NoUser() rejects, warn on the ephemeral key fallback, and keep a manager's delete-by-ref-id from wiping its own OP cookie.
2026-06-05 21:03:38 +02:00
Ömer Duran
cdc671677d
Auth: Let the cluster JWT sync privilege-level user fields
Thread admin authorization into SaveForm via byAdmin so the user-less cluster service principal can persist login/role/WebDAV instead of silently dropping them.
2026-06-05 21:03:38 +02:00
Ömer Duran
fddab77506
Auth: Apply Portal-granted role on OIDC instance login
Honor the pp_role claim (gated by pp_issuer_kind) over the group mapping so an instance login reflects the role the Portal granted, restricted to federatable roles.
2026-06-05 21:03:36 +02:00