fix(05): WR-05 gate public file serving on is_public

This commit is contained in:
Jakub Zych
2026-09-19 15:37:04 +02:00
parent c12e6576f6
commit 56156ae7c3
5 changed files with 195 additions and 0 deletions

View File

@@ -1,11 +1,14 @@
package attach
import (
"context"
"io"
"net/http"
"path"
"strings"
"gocloud.dev/blob"
"gorm.io/gorm"
)
const defaultStaticContentType = "application/octet-stream"
@@ -14,7 +17,25 @@ const defaultStaticContentType = "application/octet-stream"
// filename is the original disk_name or a thumb_* sibling stored in the
// original's partition. The blob key is the validated 4-segment path;
// unvalidated request segments never reach NewReader (T-05-13).
//
// This is the public-disk handler: it does not consult system_files.is_public.
// Protected files must not be stored in this bucket (Winter uses a second
// disk). To 404 is_public=false rows, mount StaticHandlerPublic instead.
// Do not mount the ungated handler on the app origin — same-origin
// Content-Type from uploads is XSS-relevant.
func StaticHandler(bucket *blob.Bucket, prefix string) http.Handler {
return servePublicBlobs(bucket, prefix, nil)
}
// StaticHandlerPublic is StaticHandler plus an is_public gate. Missing
// rows and is_public=false both 404. The lookup runs before NewReader.
func StaticHandlerPublic(bucket *blob.Bucket, prefix string, db *gorm.DB) http.Handler {
return servePublicBlobs(bucket, prefix, func(ctx context.Context, filename string) (bool, error) {
return fileIsPublic(ctx, db, filename)
})
}
func servePublicBlobs(bucket *blob.Bucket, prefix string, allow func(context.Context, string) (bool, error)) http.Handler {
prefix = strings.TrimSuffix(prefix, "/")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
@@ -35,6 +56,13 @@ func StaticHandler(bucket *blob.Bucket, prefix string) http.Handler {
http.NotFound(w, r)
return
}
if allow != nil {
ok, err := allow(r.Context(), path.Base(key))
if err != nil || !ok {
http.NotFound(w, r)
return
}
}
reader, err := bucket.NewReader(r.Context(), key, nil)
if err != nil {
http.NotFound(w, r)