mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-06 20:37:54 +02:00
add option to save additional data
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
+32
-19
@@ -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(
|
||||
¤tNotableEventID,
|
||||
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,
|
||||
|
||||
@@ -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;"`
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user