diff --git a/backend/app/server.go b/backend/app/server.go
index cd5775b..af49b18 100644
--- a/backend/app/server.go
+++ b/backend/app/server.go
@@ -1577,6 +1577,57 @@ func (s *Server) renderDenyPage(
c.Data(http.StatusOK, "text/html; charset=utf-8", buf.Bytes())
c.Abort()
+
+ // log deny page visited event
+ denyPageVisitEventID := uuid.New()
+ 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
+ if !campaign.IsAnonymous.MustGet() {
+ denyPageVisitEvent = &model.CampaignEvent{
+ ID: &denyPageVisitEventID,
+ CampaignID: &campaignID,
+ RecipientID: &recipientID,
+ IP: clientIP,
+ UserAgent: userAgent,
+ EventID: eventID,
+ Data: vo.NewEmptyOptionalString1MB(),
+ }
+ } else {
+ denyPageVisitEvent = &model.CampaignEvent{
+ ID: &denyPageVisitEventID,
+ CampaignID: &campaignID,
+ RecipientID: nil,
+ IP: vo.NewEmptyOptionalString64(),
+ UserAgent: vo.NewEmptyOptionalString255(),
+ EventID: eventID,
+ Data: vo.NewEmptyOptionalString1MB(),
+ }
+ }
+
+ err = s.repositories.Campaign.SaveEvent(c, denyPageVisitEvent)
+ if err != nil {
+ s.logger.Errorw("failed to save deny page visit event",
+ "error", err,
+ "campaignID", campaignID.String(),
+ )
+ }
+
+ // check and update if most notable event for recipient
+ currentNotableEventID, _ := campaignRecipient.NotableEventID.Get()
+ if cache.IsMoreNotableCampaignRecipientEventID(¤tNotableEventID, eventID) {
+ campaignRecipient.NotableEventID.Set(*eventID)
+ err := s.repositories.CampaignRecipient.UpdateByID(c, &campaignRecipientID, campaignRecipient)
+ if err != nil {
+ s.logger.Errorw("failed to update campaign recipient notable event",
+ "error", err,
+ "campaignRecipientID", campaignRecipientID.String(),
+ )
+ }
+ }
+
// safely log with nil checks
pageName := "unknown"
if pageNameVal, err := page.Name.Get(); err == nil {
diff --git a/backend/cache/local.go b/backend/cache/local.go
index ce75cc2..24c4775 100644
--- a/backend/cache/local.go
+++ b/backend/cache/local.go
@@ -43,6 +43,7 @@ var CampaignEventPriority = map[string]int{
data.EVENT_CAMPAIGN_RECIPIENT_AFTER_PAGE_VISITED: 60,
data.EVENT_CAMPAIGN_RECIPIENT_BEFORE_PAGE_VISITED: 40,
data.EVENT_CAMPAIGN_RECIPIENT_PAGE_VISITED: 50,
+ data.EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED: 38,
data.EVENT_CAMPAIGN_RECIPIENT_EVASION_PAGE_VISITED: 35,
data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_READ: 30,
data.EVENT_CAMPAIGN_RECIPIENT_MESSAGE_FAILED: 20,
diff --git a/backend/data/events.go b/backend/data/events.go
index 579bc8d..b1e288d 100644
--- a/backend/data/events.go
+++ b/backend/data/events.go
@@ -14,6 +14,7 @@ const (
EVENT_CAMPAIGN_RECIPIENT_BEFORE_PAGE_VISITED = "campaign_recipient_before_page_visited"
EVENT_CAMPAIGN_RECIPIENT_PAGE_VISITED = "campaign_recipient_page_visited"
EVENT_CAMPAIGN_RECIPIENT_AFTER_PAGE_VISITED = "campaign_recipient_after_page_visited"
+ EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED = "campaign_recipient_deny_page_visited"
EVENT_CAMPAIGN_RECIPIENT_SUBMITTED_DATA = "campaign_recipient_submitted_data"
EVENT_CAMPAIGN_RECIPIENT_REPORTED = "campaign_recipient_reported"
EVENT_CAMPAIGN_RECIPIENT_CANCELLED = "campaign_recipient_cancelled"
@@ -34,6 +35,7 @@ var Events = []string{
EVENT_CAMPAIGN_RECIPIENT_BEFORE_PAGE_VISITED,
EVENT_CAMPAIGN_RECIPIENT_PAGE_VISITED,
EVENT_CAMPAIGN_RECIPIENT_AFTER_PAGE_VISITED,
+ EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED,
EVENT_CAMPAIGN_RECIPIENT_SUBMITTED_DATA,
EVENT_CAMPAIGN_RECIPIENT_REPORTED,
EVENT_CAMPAIGN_RECIPIENT_CANCELLED,
diff --git a/backend/proxy/proxy.go b/backend/proxy/proxy.go
index 2ccf15f..ce9a5bd 100644
--- a/backend/proxy/proxy.go
+++ b/backend/proxy/proxy.go
@@ -3457,6 +3457,9 @@ func (m *ProxyHandler) serveDenyPageResponseDirect(req *http.Request, reqCtx *Re
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(htmlContent)))
resp.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
+ // log deny page visit event
+ m.registerDenyPageVisitEventDirect(req, reqCtx)
+
return resp
}
@@ -3670,6 +3673,63 @@ func (m *ProxyHandler) renderEvasionPageTemplate(req *http.Request, reqCtx *Requ
return buf.String(), nil
}
+func (m *ProxyHandler) registerDenyPageVisitEventDirect(req *http.Request, reqCtx *RequestContext) {
+ // use cached recipient data
+ if reqCtx.CampaignRecipient == nil || reqCtx.RecipientID == nil || reqCtx.CampaignID == nil || reqCtx.Campaign == nil {
+ return
+ }
+
+ recipientID := reqCtx.RecipientID
+ campaignID := reqCtx.CampaignID
+ campaign := reqCtx.Campaign
+
+ eventID := cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_DENY_PAGE_VISITED]
+ newEventID := uuid.New()
+ clientIP := vo.NewOptionalString64Must(utils.ExtractClientIP(req))
+ userAgent := vo.NewOptionalString255Must(utils.Substring(req.UserAgent(), 0, 1000)) // MAX_USER_AGENT_SAVED equivalent
+
+ var event *model.CampaignEvent
+ if !campaign.IsAnonymous.MustGet() {
+ event = &model.CampaignEvent{
+ ID: &newEventID,
+ CampaignID: campaignID,
+ RecipientID: recipientID,
+ IP: clientIP,
+ UserAgent: userAgent,
+ EventID: eventID,
+ Data: vo.NewEmptyOptionalString1MB(),
+ }
+ } else {
+ ua := vo.NewEmptyOptionalString255()
+ event = &model.CampaignEvent{
+ ID: &newEventID,
+ CampaignID: campaignID,
+ RecipientID: nil,
+ IP: vo.NewEmptyOptionalString64(),
+ UserAgent: ua,
+ EventID: eventID,
+ Data: vo.NewEmptyOptionalString1MB(),
+ }
+ }
+
+ err := m.CampaignRepository.SaveEvent(req.Context(), event)
+ if err != nil {
+ m.logger.Errorw("failed to save deny page visit event", "error", err)
+ }
+
+ // check and update if most notable event for recipient
+ cRecipient := reqCtx.CampaignRecipient
+ currentNotableEventID, _ := cRecipient.NotableEventID.Get()
+ if cache.IsMoreNotableCampaignRecipientEventID(¤tNotableEventID, eventID) {
+ cRecipient.NotableEventID.Set(*eventID)
+ campaignRecipientID := reqCtx.CampaignRecipientID
+ err := m.CampaignRecipientRepository.UpdateByID(req.Context(), campaignRecipientID, cRecipient)
+ if err != nil {
+ m.logger.Errorw("failed to update campaign recipient notable event for deny page", "error", err)
+ }
+ }
+}
+
func (m *ProxyHandler) registerEvasionPageVisitEventDirect(req *http.Request, reqCtx *RequestContext) {
// use cached recipient data
if reqCtx.CampaignRecipient == nil || reqCtx.RecipientID == nil || reqCtx.CampaignID == nil || reqCtx.Campaign == nil {
diff --git a/frontend/src/lib/components/EventTimeline.svelte b/frontend/src/lib/components/EventTimeline.svelte
index 17c21cb..9a9223f 100644
--- a/frontend/src/lib/components/EventTimeline.svelte
+++ b/frontend/src/lib/components/EventTimeline.svelte
@@ -45,6 +45,7 @@
campaign_recipient_before_page_visited: true,
campaign_recipient_page_visited: true,
campaign_recipient_after_page_visited: true,
+ campaign_recipient_deny_page_visited: true,
campaign_recipient_submitted_data: true,
campaign_recipient_reported: true
};
@@ -741,9 +742,11 @@
campaign_recipient_message_sent: '#94cae6',
campaign_recipient_message_failed: '#f2bb58',
campaign_recipient_message_read: '#4cb5b5',
+ campaign_recipient_evasion_page_visited: '#c8a2f0',
campaign_recipient_before_page_visited: '#eea5fa',
campaign_recipient_page_visited: '#f96dcf',
campaign_recipient_after_page_visited: '#f6287b',
+ campaign_recipient_deny_page_visited: '#ff6b35',
campaign_recipient_submitted_data: '#f42e41',
campaign_recipient_reported: '#2c3e50'
};
@@ -770,12 +773,16 @@
'',
campaign_recipient_message_read:
'',
+ campaign_recipient_evasion_page_visited:
+ '',
campaign_recipient_before_page_visited:
'',
campaign_recipient_page_visited:
'',
campaign_recipient_after_page_visited:
'',
+ campaign_recipient_deny_page_visited:
+ '',
campaign_recipient_submitted_data:
'',
campaign_recipient_reported:
@@ -798,9 +805,11 @@
campaign_recipient_message_sent: 'Email successfully delivered',
campaign_recipient_message_failed: 'Email delivery failed',
campaign_recipient_message_read: 'Recipient opened the email',
+ campaign_recipient_evasion_page_visited: 'Recipient encountered evasion page',
campaign_recipient_before_page_visited: 'Recipient browsing before target page',
campaign_recipient_page_visited: 'Recipient visited the target page',
campaign_recipient_after_page_visited: 'Recipient continued browsing after target',
+ campaign_recipient_deny_page_visited: 'Recipient was denied access',
campaign_recipient_submitted_data: 'Recipient submitted form data',
campaign_recipient_reported: 'Email was reported as spam'
};
@@ -1001,6 +1010,15 @@
/>
After Page Visited
+