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
··· 4 "context" 5 "encoding/json" 6 "fmt" 7 "log" 8 "net/http" 9 "net/url" ··· 133 }) 134 } 135 136 - // TODO: proper statuses here on early exit 137 func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) { 138 - tagParam := chi.URLParam(r, "tag") 139 - filename := chi.URLParam(r, "file") 140 f, err := rp.repoResolver.Resolve(r) 141 if err != nil { 142 log.Println("failed to get repo and knot", err) 143 return 144 } 145 146 tag, err := rp.resolveTag(r.Context(), f, tagParam) 147 if err != nil { 148 log.Println("failed to resolve tag", err) ··· 150 return 151 } 152 153 - client, err := rp.oauth.AuthorizedClient(r) 154 - if err != nil { 155 - log.Println("failed to get authorized client", err) 156 - return 157 - } 158 - 159 artifacts, err := db.GetArtifact( 160 rp.db, 161 db.FilterEq("repo_at", f.RepoAt()), ··· 164 ) 165 if err != nil { 166 log.Println("failed to get artifacts", err) 167 return 168 } 169 if len(artifacts) != 1 { 170 - log.Printf("too many or too little artifacts found") 171 return 172 } 173 174 artifact := artifacts[0] 175 176 - getBlobResp, err := client.SyncGetBlob(r.Context(), artifact.BlobCid.String(), artifact.Did) 177 if err != nil { 178 - log.Println("failed to get blob from pds", err) 179 return 180 } 181 182 - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename)) 183 - w.Write(getBlobResp) 184 } 185 186 // TODO: proper statuses here on early exit
··· 4 "context" 5 "encoding/json" 6 "fmt" 7 + "io" 8 "log" 9 "net/http" 10 "net/url" ··· 134 }) 135 } 136 137 func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) { 138 f, err := rp.repoResolver.Resolve(r) 139 if err != nil { 140 log.Println("failed to get repo and knot", err) 141 + http.Error(w, "failed to resolve repo", http.StatusInternalServerError) 142 return 143 } 144 145 + tagParam := chi.URLParam(r, "tag") 146 + filename := chi.URLParam(r, "file") 147 + 148 tag, err := rp.resolveTag(r.Context(), f, tagParam) 149 if err != nil { 150 log.Println("failed to resolve tag", err) ··· 152 return 153 } 154 155 artifacts, err := db.GetArtifact( 156 rp.db, 157 db.FilterEq("repo_at", f.RepoAt()), ··· 160 ) 161 if err != nil { 162 log.Println("failed to get artifacts", err) 163 + http.Error(w, "failed to get artifact", http.StatusInternalServerError) 164 return 165 } 166 + 167 if len(artifacts) != 1 { 168 + log.Printf("too many or too few artifacts found") 169 + http.Error(w, "artifact not found", http.StatusNotFound) 170 return 171 } 172 173 artifact := artifacts[0] 174 175 + ownerPds := f.OwnerId.PDSEndpoint() 176 + url, _ := url.Parse(fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob", ownerPds)) 177 + q := url.Query() 178 + q.Set("cid", artifact.BlobCid.String()) 179 + q.Set("did", artifact.Did) 180 + url.RawQuery = q.Encode() 181 + 182 + req, err := http.NewRequest(http.MethodGet, url.String(), nil) 183 if err != nil { 184 + log.Println("failed to create request", err) 185 + http.Error(w, "failed to create request", http.StatusInternalServerError) 186 return 187 } 188 + req.Header.Set("Content-Type", "application/json") 189 190 + resp, err := http.DefaultClient.Do(req) 191 + if err != nil { 192 + log.Println("failed to make request", err) 193 + http.Error(w, "failed to make request to PDS", http.StatusInternalServerError) 194 + return 195 + } 196 + defer resp.Body.Close() 197 + 198 + // copy status code and relevant headers from upstream response 199 + w.WriteHeader(resp.StatusCode) 200 + for key, values := range resp.Header { 201 + for _, v := range values { 202 + w.Header().Add(key, v) 203 + } 204 + } 205 + 206 + // stream the body directly to the client 207 + if _, err := io.Copy(w, resp.Body); err != nil { 208 + log.Println("error streaming response to client:", err) 209 + } 210 } 211 212 // TODO: proper statuses here on early exit
+2 -3
appview/repo/router.go
··· 21 r.Route("/tags", func(r chi.Router) { 22 r.Get("/", rp.RepoTags) 23 r.Route("/{tag}", func(r chi.Router) { 24 - r.Use(middleware.AuthMiddleware(rp.oauth)) 25 - // require auth to download for now 26 r.Get("/download/{file}", rp.DownloadArtifact) 27 28 // require repo:push to upload or delete artifacts ··· 30 // additionally: only the uploader can truly delete an artifact 31 // (record+blob will live on their pds) 32 r.Group(func(r chi.Router) { 33 - r.With(mw.RepoPermissionMiddleware("repo:push")) 34 r.Post("/upload", rp.AttachArtifact) 35 r.Delete("/{file}", rp.DeleteArtifact) 36 })
··· 21 r.Route("/tags", func(r chi.Router) { 22 r.Get("/", rp.RepoTags) 23 r.Route("/{tag}", func(r chi.Router) { 24 r.Get("/download/{file}", rp.DownloadArtifact) 25 26 // require repo:push to upload or delete artifacts ··· 28 // additionally: only the uploader can truly delete an artifact 29 // (record+blob will live on their pds) 30 r.Group(func(r chi.Router) { 31 + r.Use(middleware.AuthMiddleware(rp.oauth)) 32 + r.Use(mw.RepoPermissionMiddleware("repo:push")) 33 r.Post("/upload", rp.AttachArtifact) 34 r.Delete("/{file}", rp.DeleteArtifact) 35 })