* 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
TestIsWebDAVPath hardcoded the "/i/" proxy prefix, so it failed when
proxy.DefaultPathPrefix changed. Build the proxy cases from proxy.PathPrefix,
the same value IsWebDAVPath uses, so they track the configured prefix.
WebDAVSetFavoriteFlag logged sidecar mkdir/write failures at error level on
the UI log stream with the raw error, leaking the absolute sidecar path.
Route them through event.SystemError with clean.Error, matching the request
error handling in the WebDAV logger.
All WebDAV write-method errors (PUT/DELETE/MOVE/COPY/MKCOL/...) now go to
the console-only system log instead of the in-app errors table and browser
log viewer, because golang.org/x/net/webdav embeds absolute server paths in
its messages. Operators still see full detail in the container/service logs.
A MKCOL against an already-existing collection is a benign probe used by
sync clients such as PhotoSync to test for a directory before creating it;
it returns 405 and is logged at debug rather than as an error.
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.
- 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.
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.
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.
Cap the WebDAV PUT request body at OriginalsLimitBytes when an originals limit is configured, so a single upload cannot stream an unbounded body to disk past the per-file ceiling; no-op when unlimited. Adds a load-bearing regression test.
Normalize separators, reject drive-letter prefixes, and verify
containment with filepath.Rel instead of a string prefix, matching the
pkg/fs ZIP safe-join. Extend TestJoinUnderBase to mirror TestSafeJoin
coverage and lock the handler surface via WebDAVFileName.
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.
Move the Portal OIDC OP authorize/token/userinfo onto the shared CE
/api/v1/oauth/* routes (the reserved #4368/#4369 stubs) and drop the parallel
root /oauth/* set.
- Add overridable handler hooks (oauth_handlers.go) mirroring
server.WebDAVHandler; OAuthToken delegates only the authorization_code grant
(gated on a form content type) and keeps client_credentials/password/session
unchanged; userinfo now serves GET and POST.
- Scope the OP session cookie to /api/v1/oauth (base-path aware) so the browser
still sends it to the moved authorize endpoint instead of looping.
- Stop the proxy re-scoping instance->Portal authorize redirects
(portalRootPathPrefixes -> /api/v1/oauth/).
- Point discovery at issuer + /api/v1/oauth/*; fix the JWKS URL base-path
doubling for sub-path deployments (also in CE openid.go).
Replace strings.Split with the lazy strings.SplitSeq iterator (Go 1.23+)
in loops that only range over the tokens, avoiding the intermediate
slice allocation. The yielded substrings are identical to Split, so
behavior is unchanged. Applies the suggestion from #5599 to its three
sites (CIDR parsing, WebDAV hidden-path, overlay path checks) and
extends it to two more split-and-iterate loops in the server compression
negotiator and MCP config-options builder for consistency. The slice
returning splitPath helper is intentionally left on strings.Split.
Sweeps top-level CODEMAP/GLOSSARY, package READMEs under internal/,
and a handful of source-comment references that linked to the private
specs/ subrepo. AGENTS.md hints stay (agent-instruction document) and
the agent-skill linking targets in Makefile are guarded with
directory checks.
internal/server/README.md still listed both `If-Modified-Since` and
`If-None-Match` as active validators for `PrecompressedStatic`. The
handler does not set an `ETag`, so the active validator pair on
`/static/*` and `/c/static/*` is `Last-Modified` + `If-Modified-Since`
only — matching the doc comment in `static_precompressed.go` and the
"Conditional requests" subsection of `specs/platform/http-compression.md`.
Adds PrecompressedStatic handler in internal/server/static_precompressed.go,
wired by routes_static.go for /static/*filepath (bundled) and
/c/static/*filepath (custom). When a .zst or .gz sibling is on disk and
the client's Accept-Encoding allows it, the handler streams the
sibling via http.ServeContent — preserving Last-Modified and
If-Modified-Since revalidation, deriving Content-Type from the identity
filename so service-worker registration and SRI continue to work, and
setting an explicit Content-Length that http.ServeContent omits when
Content-Encoding is in effect.
Range requests always serve identity (the byte offsets in Content-Range
correspond to identity bytes), and PHOTOPRISM_HTTP_COMPRESSION=none
disables encoded-sibling selection so the operator switch stays
consistent with the runtime middleware. Vary: Accept-Encoding is
emitted only when the response could realistically vary by encoding.
Both /static/* and /c/static/* are added to NewShouldCompressFn's
exclusion list so the runtime middleware never re-encodes an
already-encoded body.
Negotiates zstd alongside gzip via PHOTOPRISM_HTTP_COMPRESSION as a
comma-separated preference list (e.g. "zstd,gzip"); legacy "gzip",
"none", and the empty string keep behaving as before. Bypasses
Connection: Upgrade, predicate-excluded paths, HEAD (with Vary kept),
4xx/5xx, 206, 204, and 304. Weakens strong ETags on the encoding path
so caches don't reuse a compressed body for a different coding.
- Rewrite package doc, struct comments, log line, and route-registration
comment so they describe the MCP server as a first-class feature
rather than a pre-release prototype. The "Usage" strings on
MCPCommands / MCPServeCommand were updated separately and stay
unchanged.
- Drop "in this prototype" from the edition-advisory warning emitted by
list_config_keys; the message now reads "edition filtering is
advisory; results come from the current <edition> build metadata".
- Rewrite internal/mcp/README.md: new "Scope" section with
in-scope/out-of-scope invariants, corrected authorization summary
(admin + API client roles + manager in Pro/Portal, anonymous in
public mode for the registered read-only tools), and a clearer
"Current Capabilities" block.
Signed-off-by: Michael Mayer <michael@photoprism.app>
Run `go fix ./...` and keep mechanical modernization updates.
- Replace `interface{}` with `any` in signatures and local types
- Apply formatter/style cleanups from go1.26 tooling
- Keep `omitempty` behavior-preserving simplifications suggested by fix
- No functional feature changes intended
Validation:
- go test ./... -run '^$' -count=1 (Go 1.26.0)
- GOTOOLCHAIN=go1.24.10 go test ./... -run '^$' -count=1
Signed-off-by: Michael Mayer <michael@photoprism.app>