diff --git a/backend/app/server.go b/backend/app/server.go index 231ef23..fce1a5c 100644 --- a/backend/app/server.go +++ b/backend/app/server.go @@ -12,6 +12,7 @@ import ( "net/url" "os" "path/filepath" + "runtime/debug" "strings" textTmpl "text/template" "time" @@ -336,6 +337,10 @@ func (s *Server) Handler(c *gin.Context) { "panic", r, "host", c.Request.Host, "url", c.Request.URL.String(), + "method", c.Request.Method, + "userAgent", c.Request.UserAgent(), + "remoteAddr", c.Request.RemoteAddr, + "stack", string(debug.Stack()), ) c.Status(http.StatusInternalServerError) c.Abort() @@ -1450,31 +1455,126 @@ func (s *Server) renderDenyPage( &repository.PageOption{}, ) if err != nil { - return fmt.Errorf("failed to get landing page: %s", err) + return fmt.Errorf("failed to get deny page: %s", err) } - tmpl, err := textTmpl.New("page"). - Funcs(service.TemplateFuncs()). - Parse(page.Content.MustGet().String()) + + // get campaign recipient - there MUST be one if we're rendering a deny page + campaignRecipient, _, err := server.GetCampaignRecipientFromURLParams( + ctx, + c.Request, + s.repositories.Identifier, + s.repositories.CampaignRecipient, + ) if err != nil { - return fmt.Errorf("failed to parse page template: %s", err) + return fmt.Errorf("failed to get campaign recipient for deny page: %s", err) } - baseURL := "https://" + domain.Name - w := bytes.NewBuffer([]byte{}) - err = tmpl.Execute(w, - map[string]string{ - "BaseURL": baseURL, - }) + if campaignRecipient == nil { + return fmt.Errorf("campaign recipient is nil") + } + + // get recipient ID from campaign recipient with nil check + recipientID, err := campaignRecipient.RecipientID.Get() if err != nil { - return fmt.Errorf("failed to execute page template: %s", err) + return fmt.Errorf("campaign recipient has no recipient ID: %s", err) } - c.Data(http.StatusOK, "text/html; charset=utf-8", w.Bytes()) + recipient, err := s.repositories.Recipient.GetByID(ctx, &recipientID, &repository.RecipientOption{}) + if err != nil { + return fmt.Errorf("failed to get recipient: %s", err) + } + if recipient == nil { + return fmt.Errorf("recipient is nil") + } + + // get campaign with nil check + campaignID, err := campaignRecipient.CampaignID.Get() + if err != nil { + return fmt.Errorf("campaign recipient has no campaign ID: %s", err) + } + campaign, err := s.repositories.Campaign.GetByID(ctx, &campaignID, &repository.CampaignOption{}) + if err != nil { + return fmt.Errorf("failed to get campaign: %s", err) + } + if campaign == nil { + return fmt.Errorf("campaign is nil") + } + + // get campaign template with email and nil check + templateID, err := campaign.TemplateID.Get() + if err != nil { + return fmt.Errorf("campaign has no template ID: %s", err) + } + cTemplate, err := s.repositories.CampaignTemplate.GetByID( + ctx, + &templateID, + &repository.CampaignTemplateOption{ + WithEmail: true, + WithIdentifier: true, + }, + ) + if err != nil { + return fmt.Errorf("failed to get campaign template: %s", err) + } + if cTemplate == nil { + return fmt.Errorf("campaign template is nil") + } + + // get email with nil check + emailID, err := cTemplate.EmailID.Get() + if err != nil { + return fmt.Errorf("campaign template has no email ID: %s", err) + } + email, err := s.repositories.Email.GetByID(ctx, &emailID, &repository.EmailOption{}) + if err != nil { + return fmt.Errorf("failed to get email: %s", err) + } + if email == nil { + return fmt.Errorf("email is nil") + } + + // get campaign recipient ID with nil check + campaignRecipientID, err := campaignRecipient.ID.Get() + if err != nil { + return fmt.Errorf("campaign recipient has no ID: %s", err) + } + + // render with full template context + buf, err := s.services.Template.CreatePhishingPageWithCampaign( + domain, + email, + &campaignRecipientID, + recipient, + page.Content.MustGet().String(), + cTemplate, + "", // no state parameter for deny pages + c.Request.URL.Path, + campaign, + ) + if err != nil { + return fmt.Errorf("failed to render deny page template: %s", err) + } + + c.Data(http.StatusOK, "text/html; charset=utf-8", buf.Bytes()) c.Abort() - s.logger.Debugw("rendered deny page: %s", - "pageName", page.Name.MustGet().String(), - "pageID", page.ID.MustGet().String(), + // safely log with nil checks + pageName := "unknown" + if pageNameVal, err := page.Name.Get(); err == nil { + pageName = pageNameVal.String() + } + pageIDStr := "unknown" + if pageIDVal, err := page.ID.Get(); err == nil { + pageIDStr = pageIDVal.String() + } + recipientEmailStr := "unknown" + if recipientEmailVal, err := recipient.Email.Get(); err == nil { + recipientEmailStr = recipientEmailVal.String() + } + + s.logger.Debugw("rendered deny page", + "pageName", pageName, + "pageID", pageIDStr, + "recipientEmail", recipientEmailStr, ) return nil - } // AssignRoutes assigns the routes to the server diff --git a/backend/proxy/proxy.go b/backend/proxy/proxy.go index 197eba4..6c992cf 100644 --- a/backend/proxy/proxy.go +++ b/backend/proxy/proxy.go @@ -14,6 +14,7 @@ import ( "net/http/cookiejar" "net/url" "regexp" + "runtime/debug" "sort" "strconv" "strings" @@ -159,7 +160,24 @@ func NewProxyHandler( } // HandleHTTPRequest processes incoming http requests through the proxy -func (m *ProxyHandler) HandleHTTPRequest(w http.ResponseWriter, req *http.Request, domain *database.Domain) error { +func (m *ProxyHandler) HandleHTTPRequest(w http.ResponseWriter, req *http.Request, domain *database.Domain) (err error) { + // add panic recovery with debug trace + defer func() { + if r := recover(); r != nil { + m.logger.Errorw("proxy handler panic recovered", + "panic", r, + "host", req.Host, + "path", req.URL.Path, + "query", req.URL.RawQuery, + "method", req.Method, + "userAgent", req.UserAgent(), + "remoteAddr", req.RemoteAddr, + "stack", string(debug.Stack()), + ) + err = fmt.Errorf("proxy handler panic: %v", r) + } + }() + ctx := req.Context() // initialize request context @@ -3140,28 +3158,118 @@ func (m *ProxyHandler) serveDenyPageResponseDirect(req *http.Request, reqCtx *Re return nil } - // render the deny page - htmlContent, err := denyPage.Content.Get() + // render deny page with full template processing + htmlContent, err := m.renderDenyPageTemplate(req, reqCtx, denyPage, campaign, cTemplate) if err != nil { + m.logger.Errorw("failed to render deny page template", "error", err) return nil } - content := htmlContent.String() - // create HTTP response resp := &http.Response{ StatusCode: 200, Header: make(http.Header), - Body: io.NopCloser(strings.NewReader(content)), + Body: io.NopCloser(strings.NewReader(htmlContent)), } resp.Header.Set("Content-Type", "text/html; charset=utf-8") - resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(content))) + resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(htmlContent))) resp.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") return resp } +// renderDenyPageTemplate renders the deny page with full template processing like evasion pages +func (m *ProxyHandler) renderDenyPageTemplate(req *http.Request, reqCtx *RequestContext, page *model.Page, campaign *model.Campaign, cTemplate *model.CampaignTemplate) (string, error) { + // get recipient + ctx := req.Context() + cRecipient, err := m.CampaignRecipientRepository.GetByID(ctx, reqCtx.CampaignRecipientID, &repository.CampaignRecipientOption{}) + if err != nil { + return "", fmt.Errorf("failed to get campaign recipient: %w", err) + } + recipientID, err := cRecipient.RecipientID.Get() + if err != nil { + return "", fmt.Errorf("failed to get recipient ID: %w", err) + } + + // get recipient details + recipientRepo := repository.Recipient{DB: m.CampaignRecipientRepository.DB} + recipient, err := recipientRepo.GetByID(ctx, &recipientID, &repository.RecipientOption{}) + if err != nil { + return "", fmt.Errorf("failed to get recipient: %w", err) + } + + // get email for template + templateID, err := campaign.TemplateID.Get() + if err != nil { + return "", fmt.Errorf("failed to get template ID: %w", err) + } + + cTemplateWithEmail, err := m.CampaignTemplateRepository.GetByID(ctx, &templateID, &repository.CampaignTemplateOption{ + WithEmail: true, + }) + if err != nil { + return "", fmt.Errorf("failed to get campaign template with email: %w", err) + } + + emailID := cTemplateWithEmail.EmailID.MustGet() + emailRepo := repository.Email{DB: m.CampaignRepository.DB} + email, err := emailRepo.GetByID(ctx, &emailID, &repository.EmailOption{}) + if err != nil { + return "", fmt.Errorf("failed to get email: %w", err) + } + + // get domain + hostVO, err := vo.NewString255(req.Host) + if err != nil { + return "", fmt.Errorf("failed to create host VO: %w", err) + } + domain, err := m.DomainRepository.GetByName(ctx, hostVO, &repository.DomainOption{}) + if err != nil { + return "", fmt.Errorf("failed to get domain: %w", err) + } + + // get page content + htmlContent, err := page.Content.Get() + if err != nil { + return "", fmt.Errorf("failed to get deny page HTML content: %w", err) + } + + // convert model.Domain to database.Domain + var proxyID *uuid.UUID + if id, err := domain.ProxyID.Get(); err == nil { + proxyID = &id + } + + dbDomain := &database.Domain{ + ID: domain.ID.MustGet(), + Name: domain.Name.MustGet().String(), + Type: domain.Type.MustGet().String(), + ProxyID: proxyID, + ProxyTargetDomain: domain.ProxyTargetDomain.MustGet().String(), + HostWebsite: domain.HostWebsite.MustGet(), + RedirectURL: domain.RedirectURL.MustGet().String(), + } + + // use template service to render the deny page with full template processing + buf, err := m.TemplateService.CreatePhishingPageWithCampaign( + dbDomain, + email, + reqCtx.CampaignRecipientID, + recipient, + htmlContent.String(), + cTemplate, + "", // no state parameter for deny pages + req.URL.Path, + campaign, + ) + if err != nil { + return "", fmt.Errorf("failed to render deny page template: %w", err) + } + + return buf.String(), nil +} + func (m *ProxyHandler) renderEvasionPageTemplate(req *http.Request, reqCtx *RequestContext, page *model.Page, campaign *model.Campaign, cTemplate *model.CampaignTemplate, nextPageType string, originalURL string) (string, error) { // get recipient ctx := req.Context()