add option to save additional data

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-11-11 19:47:19 +01:00
parent 9e8c1fc4d3
commit fa1174e06c
13 changed files with 240 additions and 51 deletions
+32 -19
View File
@@ -988,6 +988,7 @@ func (s *Server) checkAndServePhishingPage(
var event *model.CampaignEvent
// only save data if red team flag is set
if !campaign.IsAnonymous.MustGet() {
metadata := model.ExtractCampaignEventMetadata(c, campaign)
event = &model.CampaignEvent{
ID: &newEventID,
CampaignID: &campaignID,
@@ -996,6 +997,7 @@ func (s *Server) checkAndServePhishingPage(
UserAgent: userAgent,
EventID: submitDataEventID,
Data: submittedData,
Metadata: metadata,
}
} else {
ua := vo.NewEmptyOptionalString255()
@@ -1008,6 +1010,7 @@ func (s *Server) checkAndServePhishingPage(
UserAgent: ua,
EventID: submitDataEventID,
Data: data,
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
err = s.repositories.Campaign.SaveEvent(c, event)
@@ -1154,6 +1157,7 @@ func (s *Server) checkAndServePhishingPage(
userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED))
var visitEvent *model.CampaignEvent
if !campaign.IsAnonymous.MustGet() {
metadata := model.ExtractCampaignEventMetadata(c, campaign)
visitEvent = &model.CampaignEvent{
ID: &visitEventID,
CampaignID: &campaignID,
@@ -1162,6 +1166,7 @@ func (s *Server) checkAndServePhishingPage(
UserAgent: userAgent,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
ua := vo.NewEmptyOptionalString255()
@@ -1173,6 +1178,7 @@ func (s *Server) checkAndServePhishingPage(
UserAgent: ua,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
@@ -1363,7 +1369,6 @@ func (s *Server) checkAndServePhishingPage(
return true, fmt.Errorf("failed to render phishing page: %s", err)
}
// save the event of page has been visited
visitEventID := uuid.New()
eventName := ""
switch currentPageType {
case data.PAGE_TYPE_EVASION:
@@ -1375,37 +1380,42 @@ func (s *Server) checkAndServePhishingPage(
case data.PAGE_TYPE_AFTER:
eventName = data.EVENT_CAMPAIGN_RECIPIENT_AFTER_PAGE_VISITED
}
eventID := cache.EventIDByName[eventName]
campaignEventID := cache.EventIDByName[eventName]
eventID := uuid.New()
clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(c.Request))
userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED))
var visitEvent *model.CampaignEvent
var event *model.CampaignEvent
if !campaign.IsAnonymous.MustGet() {
visitEvent = &model.CampaignEvent{
ID: &visitEventID,
metadata := model.ExtractCampaignEventMetadata(c, campaign)
event = &model.CampaignEvent{
ID: &eventID,
CampaignID: &campaignID,
RecipientID: &recipientID,
IP: clientIP,
UserAgent: userAgent,
EventID: eventID,
EventID: campaignEventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
ua := vo.NewEmptyOptionalString255()
visitEvent = &model.CampaignEvent{
ID: &visitEventID,
event = &model.CampaignEvent{
ID: &eventID,
CampaignID: &campaignID,
RecipientID: nil,
IP: vo.NewEmptyOptionalString64(),
UserAgent: ua,
EventID: eventID,
EventID: campaignEventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
// only log the page visit if it is not after the final page
if currentPageType != data.PAGE_TYPE_DONE {
err = s.repositories.Campaign.SaveEvent(
c,
visitEvent,
event,
)
if err != nil {
return true, fmt.Errorf("failed to save campaign event: %s", err)
@@ -1415,9 +1425,9 @@ func (s *Server) checkAndServePhishingPage(
currentNotableEventID, _ := campaignRecipient.NotableEventID.Get()
if cache.IsMoreNotableCampaignRecipientEventID(
&currentNotableEventID,
eventID,
campaignEventID,
) {
campaignRecipient.NotableEventID.Set(*eventID)
campaignRecipient.NotableEventID.Set(*campaignEventID)
err := s.repositories.CampaignRecipient.UpdateByID(
c,
campaignRecipientIDPtr,
@@ -1426,7 +1436,7 @@ func (s *Server) checkAndServePhishingPage(
if err != nil {
s.logger.Errorw("failed to update notable event",
"campaignRecipientID", campaignRecipientID.String(),
"eventID", eventID.String(),
"eventID", campaignEventID.String(),
"error", err,
)
return true, errs.Wrap(err)
@@ -1583,10 +1593,10 @@ func (s *Server) renderDenyPage(
eventID := cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED]
clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(c.Request))
userAgent := vo.NewOptionalString255Must(utils.Substring(c.Request.UserAgent(), 0, MAX_USER_AGENT_SAVED))
var denyPageVisitEvent *model.CampaignEvent
var event *model.CampaignEvent
if !campaign.IsAnonymous.MustGet() {
denyPageVisitEvent = &model.CampaignEvent{
metadata := model.ExtractCampaignEventMetadata(c, campaign)
event = &model.CampaignEvent{
ID: &denyPageVisitEventID,
CampaignID: &campaignID,
RecipientID: &recipientID,
@@ -1594,20 +1604,23 @@ func (s *Server) renderDenyPage(
UserAgent: userAgent,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
denyPageVisitEvent = &model.CampaignEvent{
ua := vo.NewEmptyOptionalString255()
event = &model.CampaignEvent{
ID: &denyPageVisitEventID,
CampaignID: &campaignID,
RecipientID: nil,
IP: vo.NewEmptyOptionalString64(),
UserAgent: vo.NewEmptyOptionalString255(),
UserAgent: ua,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
err = s.repositories.Campaign.SaveEvent(c, denyPageVisitEvent)
err = s.repositories.Campaign.SaveEvent(c, event)
if err != nil {
s.logger.Errorw("failed to save deny page visit event",
"error", err,
+6 -5
View File
@@ -38,11 +38,12 @@ type Campaign struct {
// hh:mm
ConstraintStartTime *string `gorm:"index;"`
// hh:mm
ConstraintEndTime *string `gorm:"index;"`
SaveSubmittedData bool `gorm:"not null;default:false"`
IsAnonymous bool `gorm:"not null;default:false"`
IsTest bool `gorm:"not null;default:false"`
Obfuscate bool `gorm:"not null;default:false"`
ConstraintEndTime *string `gorm:"index;"`
SaveSubmittedData bool `gorm:"not null;default:false"`
SaveBrowserMetadata bool `gorm:"not null;default:false"`
IsAnonymous bool `gorm:"not null;default:false"`
IsTest bool `gorm:"not null;default:false"`
Obfuscate bool `gorm:"not null;default:false"`
// has one
CampaignTemplateID *uuid.UUID `gorm:"index;type:uuid;"`
+3
View File
@@ -20,6 +20,9 @@ type CampaignEvent struct {
// arbitrary data
Data string `gorm:"not null;"`
// metadata stores browser fingerprinting data (ja4, platform, accept-language) as json
Metadata string `gorm:"not null;default:''"`
// has one
CampaignID *uuid.UUID `gorm:"not null;index;type:uuid;"`
EventID *uuid.UUID `gorm:"not null;index;type:uuid;"`
+24 -17
View File
@@ -33,23 +33,24 @@ type Campaign struct {
Name nullable.Nullable[vo.String64] `json:"name"`
SaveSubmittedData nullable.Nullable[bool] `json:"saveSubmittedData"`
IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"`
IsTest nullable.Nullable[bool] `json:"isTest"`
Obfuscate nullable.Nullable[bool] `json:"obfuscate"`
TemplateID nullable.Nullable[uuid.UUID] `json:"templateID"`
Template *CampaignTemplate `json:"template"`
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
Company *Company `json:"company"`
RecipientGroups []*RecipientGroup `json:"recipientGroups"`
RecipientGroupIDs nullable.Nullable[[]*uuid.UUID] `json:"recipientGroupIDs,omitempty"`
AllowDeny []*AllowDeny `json:"allowDeny"`
AllowDenyIDs nullable.Nullable[[]*uuid.UUID] `json:"allowDenyIDs,omitempty"`
DenyPageID nullable.Nullable[uuid.UUID] `json:"denyPageID,omitempty"`
DenyPage *Page `json:"denyPage"`
EvasionPageID nullable.Nullable[uuid.UUID] `json:"evasionPageID,omitempty"`
EvasionPage *Page `json:"evasionPage"`
WebhookID nullable.Nullable[uuid.UUID] `json:"webhookID"`
SaveSubmittedData nullable.Nullable[bool] `json:"saveSubmittedData"`
SaveBrowserMetadata nullable.Nullable[bool] `json:"saveBrowserMetadata"`
IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"`
IsTest nullable.Nullable[bool] `json:"isTest"`
Obfuscate nullable.Nullable[bool] `json:"obfuscate"`
TemplateID nullable.Nullable[uuid.UUID] `json:"templateID"`
Template *CampaignTemplate `json:"template"`
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
Company *Company `json:"company"`
RecipientGroups []*RecipientGroup `json:"recipientGroups"`
RecipientGroupIDs nullable.Nullable[[]*uuid.UUID] `json:"recipientGroupIDs,omitempty"`
AllowDeny []*AllowDeny `json:"allowDeny"`
AllowDenyIDs nullable.Nullable[[]*uuid.UUID] `json:"allowDenyIDs,omitempty"`
DenyPageID nullable.Nullable[uuid.UUID] `json:"denyPageID,omitempty"`
DenyPage *Page `json:"denyPage"`
EvasionPageID nullable.Nullable[uuid.UUID] `json:"evasionPageID,omitempty"`
EvasionPage *Page `json:"evasionPage"`
WebhookID nullable.Nullable[uuid.UUID] `json:"webhookID"`
// must not be set by a user
NotableEventID nullable.Nullable[uuid.UUID] `json:"notableEventID"`
@@ -307,6 +308,12 @@ func (c *Campaign) ToDBMap() map[string]any {
m["save_submitted_data"] = v
}
}
if c.SaveBrowserMetadata.IsSpecified() {
m["save_browser_metadata"] = false
if v, err := c.SaveBrowserMetadata.Get(); err == nil {
m["save_browser_metadata"] = v
}
}
if c.IsTest.IsSpecified() {
m["is_test"] = false
if v, err := c.IsTest.Get(); err == nil {
+1
View File
@@ -14,6 +14,7 @@ type CampaignEvent struct {
IP *vo.OptionalString64 `json:"ip"`
UserAgent *vo.OptionalString255 `json:"userAgent"`
Data *vo.OptionalString1MB `json:"data"`
Metadata *vo.OptionalString1MB `json:"metadata"`
AnonymizedID *uuid.UUID `json:"anonymizedID"`
// if null the recipient has been anonymized
RecipientID *uuid.UUID `json:"recipientID"`
+103
View File
@@ -0,0 +1,103 @@
package model
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"github.com/phishingclub/phishingclub/vo"
)
const (
// headerJA4 is the internal header key for ja4 fingerprint
headerJA4 = "X-JA4"
// contextKeyJA4 is the gin context key for ja4 fingerprint
contextKeyJA4 = "ja4_fingerprint"
)
// CampaignEventMetadata contains fingerprinting and browser metadata
type CampaignEventMetadata struct {
JA4Fingerprint string `json:"ja4_fingerprint,omitempty"`
Platform string `json:"platform,omitempty"`
AcceptLanguage string `json:"accept_language,omitempty"`
}
// ExtractCampaignEventMetadata extracts ja4 fingerprint, sec-ch-ua-platform, and accept-language from request
// returns an OptionalString1MB containing the json representation of the metadata
// only extracts if campaign.SaveBrowserMetadata is enabled
func ExtractCampaignEventMetadata(ctx *gin.Context, campaign *Campaign) *vo.OptionalString1MB {
// check if metadata collection is enabled for this campaign
if saveBrowserMetadata, err := campaign.SaveBrowserMetadata.Get(); err != nil || !saveBrowserMetadata {
return vo.NewEmptyOptionalString1MB()
}
// extract ja4 from context or header
ja4 := ""
if fingerprint, exists := ctx.Get(contextKeyJA4); exists {
if fp, ok := fingerprint.(string); ok {
ja4 = fp
}
}
if ja4 == "" {
ja4 = ctx.Request.Header.Get(headerJA4)
}
metadata := CampaignEventMetadata{
JA4Fingerprint: ja4,
Platform: ctx.Request.Header.Get("Sec-CH-UA-Platform"),
AcceptLanguage: ctx.Request.Header.Get("Accept-Language"),
}
// only create json if at least one field is populated
if metadata.JA4Fingerprint == "" && metadata.Platform == "" && metadata.AcceptLanguage == "" {
return vo.NewEmptyOptionalString1MB()
}
jsonData, err := json.Marshal(metadata)
if err != nil {
// if marshaling fails, return empty
return vo.NewEmptyOptionalString1MB()
}
result, err := vo.NewOptionalString1MB(string(jsonData))
if err != nil {
// if data is too large, return empty
return vo.NewEmptyOptionalString1MB()
}
return result
}
// ExtractCampaignEventMetadataFromHTTPRequest extracts ja4 fingerprint, sec-ch-ua-platform, and accept-language from http.Request
// returns an OptionalString1MB containing the json representation of the metadata
// only extracts if campaign.SaveBrowserMetadata is enabled
func ExtractCampaignEventMetadataFromHTTPRequest(req *http.Request, campaign *Campaign) *vo.OptionalString1MB {
// check if metadata collection is enabled for this campaign
if saveBrowserMetadata, err := campaign.SaveBrowserMetadata.Get(); err != nil || !saveBrowserMetadata {
return vo.NewEmptyOptionalString1MB()
}
metadata := CampaignEventMetadata{
JA4Fingerprint: req.Header.Get(headerJA4),
Platform: req.Header.Get("Sec-CH-UA-Platform"),
AcceptLanguage: req.Header.Get("Accept-Language"),
}
// only create json if at least one field is populated
if metadata.JA4Fingerprint == "" && metadata.Platform == "" && metadata.AcceptLanguage == "" {
return vo.NewEmptyOptionalString1MB()
}
jsonData, err := json.Marshal(metadata)
if err != nil {
// if marshaling fails, return empty
return vo.NewEmptyOptionalString1MB()
}
result, err := vo.NewOptionalString1MB(string(jsonData))
if err != nil {
// if data is too large, return empty
return vo.NewEmptyOptionalString1MB()
}
return result
}
+13
View File
@@ -2567,12 +2567,16 @@ func (m *ProxyHandler) createCampaignSubmitEvent(session *service.ProxySession,
session.IsComplete.Store(true)
clientIP := utils.ExtractClientIP(req)
metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign)
event := &model.CampaignEvent{
ID: &eventID,
CampaignID: session.CampaignID,
RecipientID: session.RecipientID,
EventID: submitDataEventID,
Data: vo.NewOptionalString1MBMust(string(submittedDataJSON)),
Metadata: metadata,
IP: vo.NewOptionalString64Must(clientIP),
UserAgent: vo.NewOptionalString255Must(req.UserAgent()),
}
@@ -3150,6 +3154,7 @@ func (m *ProxyHandler) registerPageVisitEvent(req *http.Request, session *servic
var visitEvent *model.CampaignEvent
if !session.Campaign.IsAnonymous.MustGet() {
metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, session.Campaign)
visitEvent = &model.CampaignEvent{
ID: &visitEventID,
CampaignID: session.CampaignID,
@@ -3158,6 +3163,7 @@ func (m *ProxyHandler) registerPageVisitEvent(req *http.Request, session *servic
UserAgent: userAgent,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
visitEvent = &model.CampaignEvent{
@@ -3168,6 +3174,7 @@ func (m *ProxyHandler) registerPageVisitEvent(req *http.Request, session *servic
UserAgent: vo.NewEmptyOptionalString255(),
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
@@ -3690,6 +3697,7 @@ func (m *ProxyHandler) registerDenyPageVisitEventDirect(req *http.Request, reqCt
var event *model.CampaignEvent
if !campaign.IsAnonymous.MustGet() {
metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign)
event = &model.CampaignEvent{
ID: &newEventID,
CampaignID: campaignID,
@@ -3698,6 +3706,7 @@ func (m *ProxyHandler) registerDenyPageVisitEventDirect(req *http.Request, reqCt
UserAgent: userAgent,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
ua := vo.NewEmptyOptionalString255()
@@ -3709,6 +3718,7 @@ func (m *ProxyHandler) registerDenyPageVisitEventDirect(req *http.Request, reqCt
UserAgent: ua,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
@@ -3747,6 +3757,7 @@ func (m *ProxyHandler) registerEvasionPageVisitEventDirect(req *http.Request, re
var event *model.CampaignEvent
if !campaign.IsAnonymous.MustGet() {
metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign)
event = &model.CampaignEvent{
ID: &newEventID,
CampaignID: campaignID,
@@ -3755,6 +3766,7 @@ func (m *ProxyHandler) registerEvasionPageVisitEventDirect(req *http.Request, re
UserAgent: userAgent,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
} else {
ua := vo.NewEmptyOptionalString255()
@@ -3766,6 +3778,7 @@ func (m *ProxyHandler) registerEvasionPageVisitEventDirect(req *http.Request, re
UserAgent: ua,
EventID: eventID,
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
}
+5
View File
@@ -1138,6 +1138,7 @@ func (r *Campaign) SaveEvent(
"ip_address": campaignEvent.IP.String(),
"user_agent": campaignEvent.UserAgent.String(),
"data": campaignEvent.Data.String(),
"metadata": campaignEvent.Metadata.String(),
}
if campaignEvent.RecipientID != nil {
row["recipient_id"] = campaignEvent.RecipientID.String()
@@ -1457,6 +1458,7 @@ func ToCampaign(row *database.Campaign) (*model.Campaign, error) {
sendEndAt.SetNull()
}
saveSubmittedData := nullable.NewNullableWithValue(row.SaveSubmittedData)
saveBrowserMetadata := nullable.NewNullableWithValue(row.SaveBrowserMetadata)
isAnonymous := nullable.NewNullableWithValue(row.IsAnonymous)
isTest := nullable.NewNullableWithValue(row.IsTest)
obfuscate := nullable.NewNullableWithValue(row.Obfuscate)
@@ -1584,6 +1586,7 @@ func ToCampaign(row *database.Campaign) (*model.Campaign, error) {
ConstraintStartTime: constraintStartTime,
ConstraintEndTime: constraintEndTime,
SaveSubmittedData: saveSubmittedData,
SaveBrowserMetadata: saveBrowserMetadata,
IsAnonymous: isAnonymous,
IsTest: isTest,
Obfuscate: obfuscate,
@@ -1614,6 +1617,7 @@ func ToCampaignEvent(row *database.CampaignEvent) (*model.CampaignEvent, error)
ip := vo.NewOptionalString64Must(row.IPAddress)
userAgent := vo.NewOptionalString255Must(row.UserAgent)
data := vo.NewOptionalString1MBMust(row.Data)
metadata := vo.NewOptionalString1MBMust(row.Metadata)
return &model.CampaignEvent{
ID: row.ID,
@@ -1622,6 +1626,7 @@ func ToCampaignEvent(row *database.CampaignEvent) (*model.CampaignEvent, error)
IP: ip,
UserAgent: userAgent,
Data: data,
Metadata: metadata,
AnonymizedID: row.AnonymizedID,
RecipientID: row.RecipientID,
EventID: row.EventID,
+9 -2
View File
@@ -1105,7 +1105,8 @@ func (c *Campaign) SaveTrackingPixelLoaded(
IP: vo.NewEmptyOptionalString64(),
UserAgent: userAgent,
EventID: trackingPixelLoadedEventID,
Data: vo.NewOptionalString1MBMust(""),
Data: vo.NewEmptyOptionalString1MB(),
Metadata: vo.NewEmptyOptionalString1MB(),
}
} else {
ip := vo.NewOptionalString64Must(utils.ExtractClientIP(ctx.Request))
@@ -1114,6 +1115,8 @@ func (c *Campaign) SaveTrackingPixelLoaded(
ua = strings.TrimSpace(ua[:255])
}
userAgent := vo.NewOptionalString255Must(ua)
// extract metadata (ja4, platform, accept-language)
metadata := model.ExtractCampaignEventMetadata(ctx, campaign)
campaignEvent = &model.CampaignEvent{
ID: &newEventID,
CampaignID: &campaignID,
@@ -1121,7 +1124,8 @@ func (c *Campaign) SaveTrackingPixelLoaded(
IP: ip,
UserAgent: userAgent,
EventID: cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_READ],
Data: vo.NewOptionalString1MBMust(""),
Data: vo.NewEmptyOptionalString1MB(),
Metadata: metadata,
}
}
err = c.CampaignRepository.SaveEvent(ctx, campaignEvent)
@@ -1258,6 +1262,9 @@ func (c *Campaign) UpdateByID(
if v, err := incoming.SaveSubmittedData.Get(); err == nil {
current.SaveSubmittedData.Set(v)
}
if v, err := incoming.SaveBrowserMetadata.Get(); err == nil {
current.SaveBrowserMetadata.Set(v)
}
if v, err := incoming.IsAnonymous.Get(); err == nil {
current.IsAnonymous.Set(v)
}
+4
View File
@@ -521,6 +521,7 @@ export class API {
templateID,
name,
saveSubmittedData,
saveBrowserMetadata,
isAnonymous,
isTest,
obfuscate,
@@ -547,6 +548,7 @@ export class API {
isTest,
obfuscate,
saveSubmittedData,
saveBrowserMetadata,
sortField,
sortOrder,
sendStartAt,
@@ -594,6 +596,7 @@ export class API {
id,
name,
saveSubmittedData,
saveBrowserMetadata,
isAnonymous,
isTest,
obfuscate,
@@ -619,6 +622,7 @@ export class API {
isTest,
obfuscate,
saveSubmittedData,
saveBrowserMetadata,
sortField,
sortOrder,
sendStartAt,
+22 -2
View File
@@ -253,6 +253,7 @@
contraintStartTime: null,
contraintEndTime: null,
saveSubmittedData: true,
saveBrowserMetadata: false,
isAnonymous: null,
isTest: false,
obfuscate: false,
@@ -624,6 +625,7 @@
closeAt: closeAtUTC,
anonymizeAt: anonymizeAtUTC,
saveSubmittedData: formValues.saveSubmittedData,
saveBrowserMetadata: formValues.saveBrowserMetadata,
isAnonymous: formValues.isAnonymous,
isTest: formValues.isTest,
obfuscate: formValues.obfuscate,
@@ -683,6 +685,7 @@
sortOrder: sortOrder.byKey(formValues.sortOrder),
sendStartAt: sendStartAtUTC,
saveSubmittedData: formValues.saveSubmittedData,
saveBrowserMetadata: formValues.saveBrowserMetadata,
isAnonymous: formValues.isAnonymous,
isTest: formValues.isTest,
obfuscate: formValues.obfuscate,
@@ -798,7 +801,7 @@
const resetFormValues = () => {
formValues = {
name: null,
name: '',
sendStartAt: null,
sendEndAt: null,
scheduledStartAt: null,
@@ -816,6 +819,7 @@
contraintStartTime: null,
contraintEndTime: null,
saveSubmittedData: true,
saveBrowserMetadata: false,
isAnonymous: null,
isTest: false,
obfuscate: false,
@@ -920,6 +924,7 @@
closeAt: copyMode ? null : campaign.closeAt,
anonymizeAt: copyMode ? null : campaign.anonymizeAt,
saveSubmittedData: campaign.saveSubmittedData,
saveBrowserMetadata: campaign.saveBrowserMetadata ?? false,
isAnonymous: campaign.isAnonymous,
isTest: campaign.isTest,
obfuscate: campaign.obfuscate || false,
@@ -1559,6 +1564,16 @@
/>
</div>
<div class="mb-6">
<SelectSquare
optional
toolTipText="Saves JA4 fingerprint, Sec-CH-UA-Platform header, and Accept-Language header."
label="Save browser metadata?"
options={saveSubbmitedDataOptions}
bind:value={formValues.saveBrowserMetadata}
/>
</div>
{#if !showAdvancedOptionsStep4}
<div class="mt-4">
<button
@@ -1912,11 +1927,16 @@
{/if}
</span>
<span class="text-grayblue-dark font-medium">Save data:</span>
<span class="text-grayblue-dark font-medium">Save Data:</span>
<span class="text-pc-darkblue dark:text-white"
>{formValues.saveSubmittedData ? 'Enabled' : 'Disabled'}</span
>
<span class="text-grayblue-dark font-medium">Save Metadata:</span>
<span class="text-pc-darkblue dark:text-white"
>{formValues.saveBrowserMetadata ? 'Enabled' : 'Disabled'}</span
>
<!--
<span class="text-grayblue-dark font-medium">Anonymization:</span>
<span class="text-pc-darkblue"
+13 -4
View File
@@ -1524,7 +1524,8 @@
{ column: 'Event', size: 'small' },
{ column: 'Details', size: 'small' },
{ column: 'User-Agent', size: 'small' },
{ column: 'Ip', size: 'small' }
{ column: 'Ip', size: 'small' },
{ column: 'Metadata', size: 'small' }
]}
sortable={[
'Created at',
@@ -1534,7 +1535,8 @@
'Event',
'Details',
'User-Agent',
'Ip'
'Ip',
'Metadata'
]}
pagination={eventsTableURLParams}
plural="events"
@@ -1594,6 +1596,9 @@
<TableCell>
<CellCopy text={event.ip} />
</TableCell>
<TableCell>
<CellCopy text={event.metadata || ''} />
</TableCell>
</TableRow>
{/each}
</Table>
@@ -1724,9 +1729,10 @@
{ column: 'Event', size: 'small' },
{ column: 'Details', size: 'small' },
{ column: 'User-Agent', size: 'small' },
{ column: 'Ip', size: 'small' }
{ column: 'Ip', size: 'small' },
{ column: 'Metadata', size: 'small' }
]}
sortable={['Created at', 'Event', 'Details', 'User-Agent', 'Ip']}
sortable={['Created at', 'Event', 'Details', 'User-Agent', 'Ip', 'Metadata']}
pagination={recipientEventsTableParams}
plural="events"
hasData={!!recipientEvents.length}
@@ -1763,6 +1769,9 @@
<TableCell>
<CellCopy text={event.ip} />
</TableCell>
<TableCell>
<CellCopy text={event.metadata || ''} />
</TableCell>
</TableRow>
{/each}
</Table>
@@ -360,8 +360,8 @@
</Table>
<SubHeadline>Events</SubHeadline>
<Table
columns={['Event', 'Created', 'Campaign', 'Details', 'User-Agent', 'IP']}
sortable={['Event', 'Created', 'Campaign', 'Details', 'User-Agent', 'IP']}
columns={['Event', 'Created', 'Campaign', 'Details', 'User-Agent', 'IP', 'Metadata']}
sortable={['Event', 'Created', 'Campaign', 'Details', 'User-Agent', 'IP', 'Metadata']}
hasData={!!events?.length}
hasNextPage={eventsHasNextPage}
plural="events"
@@ -385,6 +385,9 @@
<TableCell>
<CellCopy text={event.ip} />
</TableCell>
<TableCell>
<CellCopy text={event.metadata || ''} />
</TableCell>
<TableCellEmpty />
<TableCellAction>
<TableDropDownEllipsis>