From b1aab0881368aa67518804555aae037b44611f88 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 16 Sep 2025 11:53:28 +0100 Subject: [PATCH] appview: relax auth requirement on artifact download Change-Id: rlorkkyzokzrnslwnutsuxylqqvouooy Signed-off-by: oppiliappan --- appview/repo/artifact.go | 54 +++++++++++++++++++++++++++++----------- appview/repo/router.go | 5 ++-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/appview/repo/artifact.go b/appview/repo/artifact.go index a16dad7d..8b9c8396 100644 --- a/appview/repo/artifact.go +++ b/appview/repo/artifact.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "log" "net/http" "net/url" @@ -133,16 +134,17 @@ func (rp *Repo) AttachArtifact(w http.ResponseWriter, r *http.Request) { }) } -// TODO: proper statuses here on early exit func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) { - tagParam := chi.URLParam(r, "tag") - filename := chi.URLParam(r, "file") f, err := rp.repoResolver.Resolve(r) if err != nil { log.Println("failed to get repo and knot", err) + http.Error(w, "failed to resolve repo", http.StatusInternalServerError) return } + tagParam := chi.URLParam(r, "tag") + filename := chi.URLParam(r, "file") + tag, err := rp.resolveTag(r.Context(), f, tagParam) if err != nil { log.Println("failed to resolve tag", err) @@ -150,12 +152,6 @@ func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) { return } - client, err := rp.oauth.AuthorizedClient(r) - if err != nil { - log.Println("failed to get authorized client", err) - return - } - artifacts, err := db.GetArtifact( rp.db, db.FilterEq("repo_at", f.RepoAt()), @@ -164,23 +160,53 @@ func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) { ) if err != nil { log.Println("failed to get artifacts", err) + http.Error(w, "failed to get artifact", http.StatusInternalServerError) return } + if len(artifacts) != 1 { - log.Printf("too many or too little artifacts found") + log.Printf("too many or too few artifacts found") + http.Error(w, "artifact not found", http.StatusNotFound) return } artifact := artifacts[0] - getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did) + ownerPds := f.OwnerId.PDSEndpoint() + url, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob", ownerPds)) + q := url.Query() + q.Set("cid", artifact.BlobCid.String()) + q.Set("did", artifact.Did) + url.RawQuery = q.Encode() + + req, err := http.NewRequest(http.MethodGet, url.String(), nil) if err != nil { - log.Println("failed to get blob from pds", err) + log.Println("failed to create request", err) + http.Error(w, "failed to create request", http.StatusInternalServerError) return } + req.Header.Set("Content-Type", "application/json") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename)) - w.Write(getBlobResp) + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Println("failed to make request", err) + http.Error(w, "failed to make request to PDS", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + // copy status code and relevant headers from upstream response + w.WriteHeader(resp.StatusCode) + for key, values := range resp.Header { + for _, v := range values { + w.Header().Add(key, v) + } + } + + // stream the body directly to the client + if _, err := io.Copy(w, resp.Body); err != nil { + log.Println("error streaming response to client:", err) + } } // TODO: proper statuses here on early exit diff --git a/appview/repo/router.go b/appview/repo/router.go index bc017462..0b1200b8 100644 --- a/appview/repo/router.go +++ b/appview/repo/router.go @@ -21,8 +21,6 @@ func (rp *Repo) Router(mw *middleware.Middleware) http.Handler { r.Route("/tags", func(r chi.Router) { r.Get("/", rp.RepoTags) r.Route("/{tag}", func(r chi.Router) { - r.Use(middleware.AuthMiddleware(rp.oauth)) - // require auth to download for now r.Get("/download/{file}", rp.DownloadArtifact) // require repo:push to upload or delete artifacts @@ -30,7 +28,8 @@ func (rp *Repo) Router(mw *middleware.Middleware) http.Handler { // additionally: only the uploader can truly delete an artifact // (record+blob will live on their pds) r.Group(func(r chi.Router) { - r.With(mw.RepoPermissionMiddleware("repo:push")) + r.Use(middleware.AuthMiddleware(rp.oauth)) + r.Use(mw.RepoPermissionMiddleware("repo:push")) r.Post("/upload", rp.AttachArtifact) r.Delete("/{file}", rp.DeleteArtifact) }) -- 2.43.0