fix: enforce workspace isolation logging
This commit is contained in:
@@ -3,6 +3,7 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -255,7 +256,7 @@ func (db *DB) GetOrgMembers(ctx context.Context, orgID uuid.UUID) ([]Membership,
|
||||
}
|
||||
|
||||
// GetOrgFiles returns files for a given organization (top-level folder listing)
|
||||
func (db *DB) GetOrgFiles(ctx context.Context, orgID uuid.UUID, path string, q string, page, pageSize int) ([]File, error) {
|
||||
func (db *DB) GetOrgFiles(ctx context.Context, orgID uuid.UUID, userID uuid.UUID, path string, q string, page, pageSize int) ([]File, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
@@ -264,22 +265,31 @@ func (db *DB) GetOrgFiles(ctx context.Context, orgID uuid.UUID, path string, q s
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
orgIDStr := orgID.String()
|
||||
userIDStr := userID.String()
|
||||
log.Printf("[DATA-ISOLATION] stage=before, action=list, orgId=%s, userId=%s, fileCount=0, path=%s", orgIDStr, userIDStr, path)
|
||||
|
||||
// Basic search and pagination. Returns only direct children of the given path.
|
||||
// For root ("/"), we want files where path doesn't contain "/" after the first character.
|
||||
// For subdirs, we want files where path starts with parent but has no additional "/" after parent.
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT id, org_id::text, user_id::text, name, path, type, size, last_modified, created_at
|
||||
FROM files
|
||||
WHERE org_id = $1
|
||||
AND path != $2
|
||||
AND (
|
||||
($2 = '/' AND path LIKE '/%' AND path NOT LIKE '/%/%')
|
||||
OR ($2 != '/' AND path LIKE $2 || '/%' AND path NOT LIKE $2 || '/%/%')
|
||||
SELECT f.id, f.org_id::text, f.user_id::text, f.name, f.path, f.type, f.size, f.last_modified, f.created_at
|
||||
FROM files f
|
||||
WHERE f.org_id = $1
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM memberships m
|
||||
WHERE m.org_id = $1 AND m.user_id = $2
|
||||
)
|
||||
AND ($3 = '' OR name ILIKE '%' || $3 || '%')
|
||||
ORDER BY CASE WHEN type = 'folder' THEN 0 ELSE 1 END, name
|
||||
LIMIT $4 OFFSET $5
|
||||
`, orgID, path, q, pageSize, offset)
|
||||
AND f.path != $3
|
||||
AND (
|
||||
($3 = '/' AND f.path LIKE '/%' AND f.path NOT LIKE '/%/%')
|
||||
OR ($3 != '/' AND f.path LIKE $3 || '/%' AND f.path NOT LIKE $3 || '/%/%')
|
||||
)
|
||||
AND ($4 = '' OR f.name ILIKE '%' || $4 || '%')
|
||||
ORDER BY CASE WHEN f.type = 'folder' THEN 0 ELSE 1 END, f.name
|
||||
LIMIT $5 OFFSET $6
|
||||
`, orgID, userID, path, q, pageSize, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -303,7 +313,11 @@ func (db *DB) GetOrgFiles(ctx context.Context, orgID uuid.UUID, path string, q s
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, rows.Err()
|
||||
err = rows.Err()
|
||||
if err == nil {
|
||||
log.Printf("[DATA-ISOLATION] stage=after, action=list, orgId=%s, userId=%s, fileCount=%d, path=%s", orgIDStr, userIDStr, len(files), path)
|
||||
}
|
||||
return files, err
|
||||
}
|
||||
|
||||
// GetUserFiles returns files for a user's personal workspace at a given path
|
||||
@@ -317,10 +331,12 @@ func (db *DB) GetUserFiles(ctx context.Context, userID uuid.UUID, path string, q
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
// Return only direct children of the given path
|
||||
log.Printf("[DATA-ISOLATION] stage=before, action=list, orgId=, userId=%s, fileCount=0, path=%s", userID.String(), path)
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT id, org_id::text, user_id::text, name, path, type, size, last_modified, created_at
|
||||
FROM files
|
||||
WHERE user_id = $1
|
||||
AND org_id IS NULL
|
||||
AND path != $2
|
||||
AND (
|
||||
($2 = '/' AND path LIKE '/%' AND path NOT LIKE '/%/%')
|
||||
@@ -353,7 +369,11 @@ func (db *DB) GetUserFiles(ctx context.Context, userID uuid.UUID, path string, q
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, rows.Err()
|
||||
err = rows.Err()
|
||||
if err == nil {
|
||||
log.Printf("[DATA-ISOLATION] stage=after, action=list, orgId=, userId=%s, fileCount=%d, path=%s", userID.String(), len(files), path)
|
||||
}
|
||||
return files, err
|
||||
}
|
||||
|
||||
// CreateFile inserts a file or folder record. orgID or userID may be nil.
|
||||
@@ -361,16 +381,21 @@ func (db *DB) CreateFile(ctx context.Context, orgID *uuid.UUID, userID *uuid.UUI
|
||||
var f File
|
||||
var orgIDVal interface{}
|
||||
var userIDVal interface{}
|
||||
orgIDStr := ""
|
||||
userIDStr := ""
|
||||
if orgID != nil {
|
||||
orgIDVal = *orgID
|
||||
orgIDStr = orgID.String()
|
||||
} else {
|
||||
orgIDVal = nil
|
||||
}
|
||||
if userID != nil {
|
||||
userIDVal = *userID
|
||||
userIDStr = userID.String()
|
||||
} else {
|
||||
userIDVal = nil
|
||||
}
|
||||
log.Printf("[DATA-ISOLATION] stage=before, action=create, orgId=%s, userId=%s, fileCount=1, path=%s", orgIDStr, userIDStr, path)
|
||||
|
||||
err := db.QueryRowContext(ctx, `
|
||||
INSERT INTO files (org_id, user_id, name, path, type, size)
|
||||
@@ -380,6 +405,7 @@ func (db *DB) CreateFile(ctx context.Context, orgID *uuid.UUID, userID *uuid.UUI
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("[DATA-ISOLATION] stage=after, action=create, orgId=%s, userId=%s, fileCount=1, path=%s", orgIDStr, userIDStr, f.Path)
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -357,6 +357,17 @@ func createOrgHandler(w http.ResponseWriter, r *http.Request, db *database.DB, a
|
||||
func listFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||
// Org ID is provided by middleware.Org
|
||||
orgID := r.Context().Value(middleware.OrgKey).(uuid.UUID)
|
||||
userIDStr, ok := middleware.GetUserID(r.Context())
|
||||
if !ok || userIDStr == "" {
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Invalid user id in context")
|
||||
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Query params: path, q (search), page, pageSize
|
||||
path := r.URL.Query().Get("path")
|
||||
if path == "" {
|
||||
@@ -372,7 +383,7 @@ func listFilesHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||
fmt.Sscanf(ps, "%d", &pageSize)
|
||||
}
|
||||
|
||||
files, err := db.GetOrgFiles(r.Context(), orgID, path, q, page, pageSize)
|
||||
files, err := db.GetOrgFiles(r.Context(), orgID, userID, path, q, page, pageSize)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to get org files")
|
||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||
|
||||
Reference in New Issue
Block a user