fix: scope marks, labels, and subjects to the authenticated user

Marks (ratings/color labels) were stored without a user column — every
user saw every other user's marks. Labels and subjects from PhotoPrism's
global endpoints leaked across users because those endpoints ignore
BasePath ACL.

Sidecar:
- Add UserName as composite primary key on Mark (photo_uid, user_name)
- Replace validateSession with resolveSession that fetches the user
  identity from PhotoPrism's session endpoint
- Filter all mark queries by user_name

Frontend:
- Filter listLabels/listSubjects through a BasePath-aware existence
  check — each label/subject is kept only if the user has at least one
  matching photo (single count=1 probe per item, batched at concurrency 8)
- Skip filtering for admin users with empty BasePath (single-user compat)

Also documents USER_BASEPATHS in .env.example — the env var that drives
per-user library isolation via PhotoPrism's auth_users.base_path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:36:18 +02:00
parent 6c96c22b33
commit 4c08eba27a
6 changed files with 109 additions and 20 deletions

View File

@@ -9,13 +9,13 @@ import (
)
// Mark mirrors the per-photo extras the web client stores via the marks
// endpoints — rating + four-colour label. PhotoUID is the row key; both
// payload fields are nullable so the sparse "no rating / no colour" state
// round-trips cleanly. The Node prototype kept this in a JSON file; we
// migrate to MariaDB here so the M4 sharing work has a real table to
// extend.
// endpoints — rating + four-colour label. Composite primary key
// (photo_uid, user_name) so each user has independent marks. Both payload
// fields are nullable so the sparse "no rating / no colour" state
// round-trips cleanly.
type Mark struct {
PhotoUID string `gorm:"primaryKey;size:64;column:photo_uid" json:"-"`
UserName string `gorm:"primaryKey;size:128;column:user_name" json:"-"`
Rating *int `gorm:"column:rating" json:"rating,omitempty"`
Color *string `gorm:"size:16;column:color" json:"color,omitempty"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`