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:
@@ -85,6 +85,37 @@ func (c *ppClient) call(ctx context.Context, method, urlPath, token string, body
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ppSessionUser is the subset of PhotoPrism's session response we need.
|
||||
type ppSessionUser struct {
|
||||
UserName string `json:"UserName"`
|
||||
UserUID string `json:"UserUID"`
|
||||
BasePath string `json:"BasePath"`
|
||||
}
|
||||
|
||||
type ppSessionResponse struct {
|
||||
User ppSessionUser `json:"user"`
|
||||
}
|
||||
|
||||
// resolveSession validates the token AND returns the authenticated user.
|
||||
// Returns nil when the token is invalid or the response can't be parsed.
|
||||
func (c *ppClient) resolveSession(ctx context.Context, token string) *ppSessionUser {
|
||||
if token == "" {
|
||||
return nil
|
||||
}
|
||||
r, err := c.call(ctx, http.MethodGet, "/api/v1/session/"+token, token, nil)
|
||||
if err != nil || !r.OK {
|
||||
return nil
|
||||
}
|
||||
var resp ppSessionResponse
|
||||
if err := json.Unmarshal(r.Body, &resp); err != nil {
|
||||
return nil
|
||||
}
|
||||
if resp.User.UserName == "" {
|
||||
return nil
|
||||
}
|
||||
return &resp.User
|
||||
}
|
||||
|
||||
// validateSession is the cheapest probe that the supplied token is live:
|
||||
// list one photo. 401 → bad/expired token. We never read the payload.
|
||||
func (c *ppClient) validateSession(ctx context.Context, token string) bool {
|
||||
|
||||
Reference in New Issue
Block a user