Add cache control to Files API static assets
Brand assets served at /api/files/v1/static had no cache headers. Introduce brand.Assets to own the embedded filesystem, content-hash ETags, and HTTP serving. Responses now carry Cache-Control and ETag so clients can cache and revalidate; stable email URLs stay revalidatable (max-age=3600, no immutable). Replace hardcoded Default*Path constants with StaticPathPrefix, logical filename constants, and StaticPath(). NewAssets validates required assets at startup so a rename fails fast instead of 404ing in sent emails. The files handler keeps routing and 404 rendering; ServeAssets sets cache headers and serves the file. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/cookiebanner"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/server/jsonutil"
|
||||
"go.probo.inc/probo/pkg/server/jsonx"
|
||||
)
|
||||
|
||||
func newCORSMiddleware(logger *log.Logger, cookieBannerSvc *cookiebanner.Service) func(http.Handler) http.Handler {
|
||||
@@ -37,32 +37,32 @@ func newCORSMiddleware(logger *log.Logger, cookieBannerSvc *cookiebanner.Service
|
||||
|
||||
bannerIDStr := chi.URLParam(r, "bannerID")
|
||||
if bannerIDStr == "" {
|
||||
jsonutil.RenderForbidden(w)
|
||||
jsonx.RenderForbidden(w)
|
||||
return
|
||||
}
|
||||
|
||||
bannerID, err := gid.ParseGID(bannerIDStr)
|
||||
if err != nil {
|
||||
jsonutil.RenderForbidden(w)
|
||||
jsonx.RenderForbidden(w)
|
||||
return
|
||||
}
|
||||
|
||||
banner, err := cookieBannerSvc.GetActiveCookieBanner(r.Context(), bannerID)
|
||||
if err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderForbidden(w)
|
||||
jsonx.RenderForbidden(w)
|
||||
return
|
||||
}
|
||||
|
||||
logger.ErrorCtx(r.Context(), "cannot load cookie banner for CORS check", log.Error(err))
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
canonicalOrigin := cookiebanner.CanonicalizeOrigin(origin)
|
||||
if banner.Origin != canonicalOrigin {
|
||||
jsonutil.RenderForbidden(w)
|
||||
jsonx.RenderForbidden(w)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/geoloc"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/server/api/clientip"
|
||||
"go.probo.inc/probo/pkg/server/jsonutil"
|
||||
"go.probo.inc/probo/pkg/server/jsonx"
|
||||
"go.probo.inc/probo/pkg/uri"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func NewMux(
|
||||
func (h *Handler) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||
if err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,17 +81,17 @@ func (h *Handler) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := h.cookieBannerSvc.GetActiveBannerConfig(r.Context(), bannerID, lang, regulation, sdkVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, cookiebanner.ErrNoPublishedVersion) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("no published version"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("no published version"))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.ErrorCtx(r.Context(), "cannot get banner config", log.Error(err), log.String("sdk_version", sdkVersion))
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -124,25 +124,25 @@ func (h *Handler) resolveCountryCode(r *http.Request) *coredata.CountryCode {
|
||||
func (h *Handler) handleGetConsent(w http.ResponseWriter, r *http.Request) {
|
||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||
if err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
return
|
||||
}
|
||||
|
||||
visitorID := chi.URLParam(r, "visitorID")
|
||||
if visitorID == "" {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("missing visitor id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("missing visitor id"))
|
||||
return
|
||||
}
|
||||
|
||||
consent, err := h.cookieBannerSvc.GetVisitorConsent(r.Context(), bannerID, visitorID)
|
||||
if err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, cookiebanner.ErrConsentNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("consent not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("consent not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (h *Handler) handleGetConsent(w http.ResponseWriter, r *http.Request) {
|
||||
log.Error(err),
|
||||
log.String("sdk_version", sdkVersionFromContext(r.Context())),
|
||||
)
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -179,13 +179,13 @@ type (
|
||||
func (h *Handler) handlePostConsent(w http.ResponseWriter, r *http.Request) {
|
||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||
if err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
return
|
||||
}
|
||||
|
||||
var body postConsentBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -214,17 +214,17 @@ func (h *Handler) handlePostConsent(w http.ResponseWriter, r *http.Request) {
|
||||
record, err := h.cookieBannerSvc.RecordConsent(r.Context(), bannerID, req)
|
||||
if err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, cookiebanner.ErrVersionNotFound) || errors.Is(err, cookiebanner.ErrVersionNotPublished) {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid version"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid version"))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.ErrorCtx(r.Context(), "cannot record consent", log.Error(err), log.String("sdk_version", sdkVersion))
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -290,23 +290,23 @@ func sanitizeInitiatorURL(raw *string) *string {
|
||||
func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Request) {
|
||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||
if err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
return
|
||||
}
|
||||
|
||||
var body reportDetectedCookiesBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(body.Cookies) == 0 {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("cookies list is empty"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("cookies list is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(body.Cookies) > maxDetectedCookiesPerRequest {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("too many cookies, maximum is %d", maxDetectedCookiesPerRequest))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("too many cookies, maximum is %d", maxDetectedCookiesPerRequest))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if len(detected) == 0 {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("no valid cookie names provided"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("no valid cookie names provided"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
|
||||
|
||||
if err := h.cookieBannerSvc.ReportDetectedCookies(r.Context(), bannerID, req); err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ func (h *Handler) handleReportDetectedCookies(w http.ResponseWriter, r *http.Req
|
||||
log.Error(err),
|
||||
log.String("sdk_version", sdkVersionFromContext(r.Context())),
|
||||
)
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -392,24 +392,24 @@ const maxDetectedTrackersPerRequest = 100
|
||||
func (h *Handler) handleReportDetectedTrackers(w http.ResponseWriter, r *http.Request) {
|
||||
bannerID, err := gid.ParseGID(chi.URLParam(r, "bannerID"))
|
||||
if err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid banner id"))
|
||||
return
|
||||
}
|
||||
|
||||
var body reportDetectedTrackersBody
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
total := len(body.Cookies) + len(body.Storage) + len(body.Resources)
|
||||
if total == 0 {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("no items provided"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("no items provided"))
|
||||
return
|
||||
}
|
||||
|
||||
if total > maxDetectedTrackersPerRequest {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("too many items, maximum is %d", maxDetectedTrackersPerRequest))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("too many items, maximum is %d", maxDetectedTrackersPerRequest))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -533,18 +533,18 @@ func (h *Handler) handleReportDetectedTrackers(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if len(req.Cookies)+len(req.Storage)+len(req.Resources) == 0 {
|
||||
jsonutil.RenderBadRequest(w, fmt.Errorf("no valid items provided"))
|
||||
jsonx.RenderBadRequest(w, fmt.Errorf("no valid items provided"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.cookieBannerSvc.ReportDetectedTrackers(r.Context(), bannerID, req); err != nil {
|
||||
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
|
||||
jsonutil.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
jsonx.RenderNotFound(w, fmt.Errorf("banner not found"))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.ErrorCtx(r.Context(), "cannot report detected trackers", log.Error(err), log.String("sdk_version", sdkVersionFromContext(r.Context())))
|
||||
jsonutil.RenderInternalServerError(w)
|
||||
jsonx.RenderInternalServerError(w)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user