Refactor viewmodels and enhance security documentation; remove unused viewmodels, add path sanitization, and implement rate limiting

This commit is contained in:
Leon Bösche
2026-01-13 22:11:02 +01:00
parent 804e994e76
commit 47e94995b5
8 changed files with 274 additions and 164 deletions

View File

@@ -29,6 +29,25 @@ import (
"go.b0esche.cloud/backend/internal/storage"
)
// sanitizePath validates and sanitizes a file path to prevent path traversal attacks.
// Returns the cleaned path or an error if the path is invalid.
func sanitizePath(inputPath string) (string, error) {
// Clean the path to resolve . and ..
cleaned := path.Clean(inputPath)
// Ensure the path doesn't try to escape the root
if strings.Contains(cleaned, "..") {
return "", fmt.Errorf("invalid path: path traversal detected")
}
// Ensure path starts with /
if !strings.HasPrefix(cleaned, "/") {
cleaned = "/" + cleaned
}
return cleaned, nil
}
// getUserWebDAVClient gets or creates a user's Nextcloud account and returns a WebDAV client for them
func getUserWebDAVClient(ctx context.Context, db *database.DB, userID uuid.UUID, nextcloudBaseURL, adminUser, adminPass string) (*storage.WebDAVClient, error) {
var user struct {
@@ -53,16 +72,12 @@ func getUserWebDAVClient(ctx context.Context, db *database.DB, userID uuid.UUID,
return nil, fmt.Errorf("failed to generate password: %w", err)
}
fmt.Printf("[DEBUG-PASSWORD-FLOW] Generated password for user %s: %s\n", ncUsername, ncPassword)
// Create Nextcloud user account
err = storage.CreateNextcloudUser(nextcloudBaseURL, adminUser, adminPass, ncUsername, ncPassword)
if err != nil {
return nil, fmt.Errorf("failed to create Nextcloud user: %w", err)
}
fmt.Printf("[DEBUG-PASSWORD-FLOW] About to store in DB - username: %s, password: %s\n", ncUsername, ncPassword)
// Update database with Nextcloud credentials
_, err = db.ExecContext(ctx,
"UPDATE users SET nextcloud_username = $1, nextcloud_password = $2 WHERE id = $3",
@@ -71,16 +86,11 @@ func getUserWebDAVClient(ctx context.Context, db *database.DB, userID uuid.UUID,
return nil, fmt.Errorf("failed to update user credentials: %w", err)
}
fmt.Printf("[DEBUG-PASSWORD-FLOW] Stored in DB successfully\n")
user.NextcloudUsername = ncUsername
user.NextcloudPassword = ncPassword
fmt.Printf("[AUTO-PROVISION] Created Nextcloud account for user %s: %s\n", userID, ncUsername)
}
fmt.Printf("[DEBUG-PASSWORD-FLOW] Retrieved from DB - username: %s, password: %s\n", user.NextcloudUsername, user.NextcloudPassword)
fmt.Printf("[DEBUG-PASSWORD-FLOW] Creating WebDAV client with URL: %s, user: %s, pass: %s\n", nextcloudBaseURL, user.NextcloudUsername, user.NextcloudPassword)
// Create user-specific WebDAV client
return storage.NewUserWebDAVClient(nextcloudBaseURL, user.NextcloudUsername, user.NextcloudPassword), nil
}
@@ -1716,6 +1726,13 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
return
}
// Sanitize path to prevent path traversal
filePath, err := sanitizePath(filePath)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid path", http.StatusBadRequest)
return
}
// Get or create user's WebDAV client
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
if err != nil {
@@ -1787,8 +1804,12 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas
return
}
// Log the requested file path for debugging
fmt.Printf("[DEBUG] Download request - User: %s, Path: %s\n", userID.String(), filePath)
// Sanitize path to prevent path traversal
filePath, err := sanitizePath(filePath)
if err != nil {
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid path", http.StatusBadRequest)
return
}
// Get or create user's WebDAV client
storageClient, err := getUserWebDAVClient(r.Context(), db, userID, cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudPass)
@@ -1800,7 +1821,6 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas
// Download from user's personal Nextcloud space
remotePath := strings.TrimPrefix(filePath, "/")
fmt.Printf("[DEBUG] Downloading from user WebDAV: /%s\n", remotePath)
resp, err := storageClient.Download(r.Context(), "/"+remotePath, r.Header.Get("Range"))
if err != nil {