Monorepo for Tangled tangled.org

appview: relax auth requirement on artifact download

Signed-off-by: oppiliappan <me@oppi.li>

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