Add detected-cookies public REST endpoint for cookie auto-discovery
The JS SDK will override document.cookie to detect unknown cookies set
by the website and report them to a new POST /{bannerID}/detected-cookies
endpoint. Reported cookies are inserted into the "Uncategorised" category
and a draft version is created so the admin can review them.
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -127,6 +127,15 @@ type (
|
|||||||
Action coredata.CookieConsentAction
|
Action coredata.CookieConsentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DetectedCookie struct {
|
||||||
|
Name string
|
||||||
|
Duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportDetectedCookiesRequest struct {
|
||||||
|
Cookies []DetectedCookie
|
||||||
|
}
|
||||||
|
|
||||||
BannerConfig struct {
|
BannerConfig struct {
|
||||||
BannerID gid.GID `json:"banner_id"`
|
BannerID gid.GID `json:"banner_id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
@@ -1717,3 +1726,62 @@ func (s *Service) RecordConsent(
|
|||||||
|
|
||||||
return record, nil
|
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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@@ -51,6 +52,7 @@ func NewMux(
|
|||||||
r.Get("/config", h.handleGetConfig)
|
r.Get("/config", h.handleGetConfig)
|
||||||
r.Get("/consents/{visitorID}", h.handleGetConsent)
|
r.Get("/consents/{visitorID}", h.handleGetConsent)
|
||||||
r.Post("/consents", h.handlePostConsent)
|
r.Post("/consents", h.handlePostConsent)
|
||||||
|
r.Post("/detected-cookies", h.handleReportDetectedCookies)
|
||||||
})
|
})
|
||||||
|
|
||||||
return r
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user