assetHandler serves a previously uploaded file asset. If the asset is marked as public, it sets caching headers that allows CDNs and browsers to cache the asset. If the asset is not marked as public, it guarantees that the asset can only be accessed by authenticated users with read access to the ass
(w http.ResponseWriter, r *http.Request)
| 169 | // If the asset is marked as public, it sets caching headers that allows CDNs and browsers to cache the asset. |
| 170 | // If the asset is not marked as public, it guarantees that the asset can only be accessed by authenticated users with read access to the asset's organization. |
| 171 | func (s *Server) assetHandler(w http.ResponseWriter, r *http.Request) error { |
| 172 | // Params |
| 173 | assetID := r.PathValue("asset_id") |
| 174 | |
| 175 | // Find the asset |
| 176 | asset, err := s.admin.DB.FindAsset(r.Context(), assetID) |
| 177 | if err != nil { |
| 178 | return httputil.Error(http.StatusNotFound, err) |
| 179 | } |
| 180 | if asset.OrganizationID == nil { |
| 181 | return httputil.Errorf(http.StatusNotFound, "the requested asset has been soft deleted") |
| 182 | } |
| 183 | |
| 184 | // Check permissions |
| 185 | claims := auth.GetClaims(r.Context()) |
| 186 | if !asset.Public && !claims.OrganizationPermissions(r.Context(), *asset.OrganizationID).ReadOrg { |
| 187 | ok, err := s.admin.DB.CheckOrganizationHasPublicProjects(r.Context(), *asset.OrganizationID) |
| 188 | if err != nil { |
| 189 | return err |
| 190 | } |
| 191 | if !ok { |
| 192 | return httputil.Errorf(http.StatusForbidden, "does not have permission to access the asset") |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // Parse the asset's path, which has the form "gs://<bucket>/<path>" |
| 197 | u, err := url.Parse(asset.Path) |
| 198 | if err != nil { |
| 199 | return err |
| 200 | } |
| 201 | |
| 202 | // Set caching headers if the asset is public |
| 203 | if asset.Public { |
| 204 | w.Header().Set("Cache-Control", "public, max-age=31536000") |
| 205 | } else { |
| 206 | w.Header().Set("Cache-Control", "no-store") |
| 207 | } |
| 208 | |
| 209 | // Set the content type header |
| 210 | found := false |
| 211 | for _, t := range supportedAssetTypes { |
| 212 | if strings.HasSuffix(u.Path, t.Extension) { // Using HasSuffix to correctly check multi-part extensions like .tar.gz |
| 213 | w.Header().Set("Content-Type", t.MIMEType) |
| 214 | found = true |
| 215 | break |
| 216 | } |
| 217 | } |
| 218 | if !found { // Fallback content type |
| 219 | w.Header().Set("Content-Type", "application/octet-stream") |
| 220 | } |
| 221 | |
| 222 | // Set the content disposition header |
| 223 | // Using "attachment" as a security measure to prevent browsers from rendering the asset, which is a security vulnerability for e.g. SVGs containing scripts. |
| 224 | w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", path.Base(u.Path))) |
| 225 | |
| 226 | // Set the status code |
| 227 | w.WriteHeader(http.StatusOK) |
| 228 |
nothing calls this directly
no test coverage detected