diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go index 88218f072..d560a3c13 100644 --- a/pkg/cookiebanner/service.go +++ b/pkg/cookiebanner/service.go @@ -127,6 +127,15 @@ type ( Action coredata.CookieConsentAction } + DetectedCookie struct { + Name string + Duration string + } + + ReportDetectedCookiesRequest struct { + Cookies []DetectedCookie + } + BannerConfig struct { BannerID gid.GID `json:"banner_id"` Version int `json:"version"` @@ -1717,3 +1726,62 @@ func (s *Service) RecordConsent( return record, nil } + +func (s *Service) ReportDetectedCookies( + ctx context.Context, + bannerID gid.GID, + req ReportDetectedCookiesRequest, +) error { + return s.pg.WithTx( + ctx, + func(ctx context.Context, tx pg.Tx) error { + var banner coredata.CookieBanner + if err := banner.LoadActiveByID(ctx, tx, bannerID); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return ErrBannerNotFound + } + return fmt.Errorf("cannot load active cookie banner: %w", err) + } + + scope := coredata.NewScopeFromObjectID(banner.ID) + + var uncategorised coredata.CookieCategory + if err := uncategorised.LoadUncategorisedByCookieBannerID(ctx, tx, scope, banner.ID); err != nil { + return fmt.Errorf("cannot load uncategorised category: %w", err) + } + + inserted := 0 + now := time.Now() + + for _, dc := range req.Cookies { + cookie := &coredata.Cookie{ + ID: gid.New(scope.GetTenantID(), coredata.CookieEntityType), + OrganizationID: banner.OrganizationID, + CookieBannerID: banner.ID, + CookieCategoryID: uncategorised.ID, + Name: dc.Name, + Duration: dc.Duration, + Description: "", + CreatedAt: now, + UpdatedAt: now, + } + + if err := cookie.Insert(ctx, tx, scope); err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + continue + } + return fmt.Errorf("cannot insert detected cookie: %w", err) + } + inserted++ + } + + if inserted > 0 { + if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, banner.ID); err != nil { + return fmt.Errorf("cannot ensure draft version: %w", err) + } + } + + return nil + }, + ) +} diff --git a/pkg/server/api/cookiebanner/v1/handler.go b/pkg/server/api/cookiebanner/v1/handler.go index dd4c174cc..a3202be7f 100644 --- a/pkg/server/api/cookiebanner/v1/handler.go +++ b/pkg/server/api/cookiebanner/v1/handler.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" "github.com/go-chi/chi/v5" @@ -51,6 +52,7 @@ func NewMux( r.Get("/config", h.handleGetConfig) r.Get("/consents/{visitorID}", h.handleGetConsent) r.Post("/consents", h.handlePostConsent) + r.Post("/detected-cookies", h.handleReportDetectedCookies) }) return r @@ -179,3 +181,71 @@ func (h *Handler) handlePostConsent(w http.ResponseWriter, r *http.Request) { }, ) } + +type detectedCookieEntry struct { + Name string `json:"name"` + Duration string `json:"duration"` +} + +type reportDetectedCookiesBody struct { + Cookies []detectedCookieEntry `json:"cookies"` +} + +const maxDetectedCookiesPerRequest = 100 + +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")) + return + } + + var body reportDetectedCookiesBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + jsonutil.RenderBadRequest(w, fmt.Errorf("invalid request body")) + return + } + + if len(body.Cookies) == 0 { + jsonutil.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)) + return + } + + detected := make([]cookiebanner.DetectedCookie, 0, len(body.Cookies)) + for _, c := range body.Cookies { + name := strings.TrimSpace(c.Name) + if name == "" { + continue + } + detected = append(detected, cookiebanner.DetectedCookie{ + Name: name, + Duration: strings.TrimSpace(c.Duration), + }) + } + + if len(detected) == 0 { + jsonutil.RenderBadRequest(w, fmt.Errorf("no valid cookie names provided")) + return + } + + req := cookiebanner.ReportDetectedCookiesRequest{ + Cookies: detected, + } + + 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")) + return + } + h.logger.ErrorCtx(r.Context(), "cannot report detected cookies", log.Error(err)) + jsonutil.RenderInternalServerError(w) + return + } + + w.WriteHeader(http.StatusNoContent) +}