appview: relax auth requirement on artifact download #579

merged
opened by oppi.li targeting master from push-rlorkkyzokzr
Changed files
+42 -17
appview
+40 -14
appview/repo/artifact.go
···
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
···
})
}
-
// 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)
return
}
tag, err := rp.resolveTag(r.Context(), f, tagParam)
if err != nil {
log.Println("failed to resolve tag", err)
···
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()),
···
)
if err != nil {
log.Println("failed to get artifacts", err)
return
}
if len(artifacts) != 1 {
-
log.Printf("too many or too little artifacts found")
return
}
artifact := artifacts[0]
-
getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did)
if err != nil {
-
log.Println("failed to get blob from pds", err)
return
}
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
-
w.Write(getBlobResp)
}
// TODO: proper statuses here on early exit
···
"context"
"encoding/json"
"fmt"
+
"io"
"log"
"net/http"
"net/url"
···
})
}
func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
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)
···
return
}
artifacts, err := db.GetArtifact(
rp.db,
db.FilterEq("repo_at", f.RepoAt()),
···
)
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 few artifacts found")
+
http.Error(w, "artifact not found", http.StatusNotFound)
return
}
artifact := artifacts[0]
+
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 create request", err)
+
http.Error(w, "failed to create request", http.StatusInternalServerError)
return
}
+
req.Header.Set("Content-Type", "application/json")
+
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
+2 -3
appview/repo/router.go
···
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
···
// 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.Post("/upload", rp.AttachArtifact)
r.Delete("/{file}", rp.DeleteArtifact)
})
···
r.Route("/tags", func(r chi.Router) {
r.Get("/", rp.RepoTags)
r.Route("/{tag}", func(r chi.Router) {
r.Get("/download/{file}", rp.DownloadArtifact)
// require repo:push to upload or delete artifacts
···
// additionally: only the uploader can truly delete an artifact
// (record+blob will live on their pds)
r.Group(func(r chi.Router) {
+
r.Use(middleware.AuthMiddleware(rp.oauth))
+
r.Use(mw.RepoPermissionMiddleware("repo:push"))
r.Post("/upload", rp.AttachArtifact)
r.Delete("/{file}", rp.DeleteArtifact)
})