feat(sidecar): enforce per-user BasePath on all filesystem mutations

Folder create/rename/delete/move, photo move, heap convert, and file
rename now reject paths outside the caller's BasePath (403). Sources
resolved via PhotoPrism UIDs are re-checked in movePhotoFiles. The
USER_BASEPATHS reconciler also sets upload_path so client-app uploads
land inside the user's subtree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 12:46:00 +02:00
parent 634abc2a95
commit 6cbabda86b
7 changed files with 122 additions and 8 deletions

View File

@@ -47,6 +47,9 @@ func handleFolderCreate(cfg *Config, pp *ppClient) gin.HandlerFunc {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
return
}
if !requireUserScope(c, cfg, abs, true) {
return
}
if _, err := os.Stat(abs); err == nil {
c.JSON(http.StatusConflict, gin.H{"error": "already exists"})
return
@@ -92,6 +95,9 @@ func handleFolderRename(cfg *Config, pp *ppClient) gin.HandlerFunc {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
return
}
if !requireUserScope(c, cfg, oldAbs, true) {
return
}
st, err := os.Stat(oldAbs)
if err != nil || !st.IsDir() {
c.JSON(http.StatusBadRequest, gin.H{"error": "not a directory"})
@@ -142,6 +148,9 @@ func handleFolderDelete(cfg *Config, pp *ppClient) gin.HandlerFunc {
c.JSON(http.StatusBadRequest, gin.H{"error": "refuse to delete root"})
return
}
if !requireUserScope(c, cfg, abs, true) {
return
}
st, err := os.Stat(abs)
if err != nil || !st.IsDir() {
c.JSON(http.StatusBadRequest, gin.H{"error": "not a directory"})