Routes the remaining credential-file, identity-provider, and
database-connection messages through event.System*, as specified in
specs/platform/logging.md: the standard logger is broadcast to the web UI log
viewer and persisted, which is the wrong audience for server paths and
identity-provider details. Covers the database password, OIDC client secret
and provider URI, cluster join token, node client secret, the OIDC
session-signal key, and closing the database connection.
Sanitizes the values these messages interpolate with clean.Log and
clean.Error, raises them from Trace so a real failure stays visible, and
corrects two misleading texts: a cluster join token is reported as invalid
rather than too short, since the format is checked as well, and the JWT
diagnostic labels the value it prints as the token scope.
Adds context and sanitization to the smart album lookup warning in the file
selection query, which logged a bare error with no indication of what failed.
Suppress gosec false positives with justified //nolint directives and
rewrite an if-else chain as a switch:
- G117: test forms marshaled with a password field to build request bodies
- G124: OIDC session cookie sets HttpOnly/SameSite, Secure is caller-controlled
- G304: OIDC key read from a config-derived path, not user input
- G703: test writes to temp paths under the test's control
- gocritic: OIDC redirect role selection if-else -> switch
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).
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.
The OP session cookie stored the raw bearer token with a full-session lifetime,
so a leak of the cookie value was a usable credential against the whole API. It
now carries an HMAC-signed reference to the session id (a hash of the token,
useless as a bearer) with a 10-minute TTL refreshed on each session GET. The key
is persisted under the Portal config keys directory. The authorize handler
resolves the session via OIDCSessionCookieSession. Needs a coordinated SSO e2e
round in the Portal test env before merge.
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).