perform validation on save

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-09-16 16:42:40 +02:00
parent d72ea4e9f5
commit c1709c24aa
5 changed files with 150 additions and 0 deletions
+2
View File
@@ -138,6 +138,7 @@ func NewServices(
CampaignRepository: repositories.Campaign,
PageRepository: repositories.Page,
CampaignTemplateService: campaignTemplate,
TemplateService: templateService,
}
domain := &service.Domain{
Common: common,
@@ -149,6 +150,7 @@ func NewServices(
CampaignTemplateService: campaignTemplate,
AssetService: asset,
FileService: file,
TemplateService: templateService,
}
email := &service.Email{
Common: common,
+24
View File
@@ -36,6 +36,7 @@ type Domain struct {
CampaignTemplateService *CampaignTemplate
AssetService *Asset
FileService *File
TemplateService *Template
}
// Create creates a new domain
@@ -60,6 +61,19 @@ func (d *Domain) Create(
// d.Logger.Debugf("failed to validate domain", "error", err)
return nil, errs.Wrap(err)
}
// validate template content if present
if pageContent, err := domain.PageContent.Get(); err == nil {
if err := d.TemplateService.ValidateDomainTemplate(pageContent.String()); err != nil {
d.Logger.Errorw("failed to validate domain page template", "error", err)
return nil, validate.WrapErrorWithField(errors.New("invalid page template: "+err.Error()), "pageContent")
}
}
if notFoundContent, err := domain.PageNotFoundContent.Get(); err == nil {
if err := d.TemplateService.ValidateDomainTemplate(notFoundContent.String()); err != nil {
d.Logger.Errorw("failed to validate domain not found template", "error", err)
return nil, validate.WrapErrorWithField(errors.New("invalid not found template: "+err.Error()), "pageNotFoundContent")
}
}
// check for uniqueness
name := domain.Name.MustGet() // safe as we have validated
_, err = d.DomainRepository.GetByName(
@@ -403,9 +417,19 @@ func (d *Domain) UpdateByID(
current.HostWebsite.Set(v)
}
if v, err := incoming.PageContent.Get(); err == nil {
// validate template content before updating
if err := d.TemplateService.ValidateDomainTemplate(v.String()); err != nil {
d.Logger.Errorw("failed to validate domain page template", "error", err)
return validate.WrapErrorWithField(errors.New("invalid page template: "+err.Error()), "pageContent")
}
current.PageContent.Set(v)
}
if v, err := incoming.PageNotFoundContent.Get(); err == nil {
// validate template content before updating
if err := d.TemplateService.ValidateDomainTemplate(v.String()); err != nil {
d.Logger.Errorw("failed to validate domain not found template", "error", err)
return validate.WrapErrorWithField(errors.New("invalid not found template: "+err.Error()), "pageNotFoundContent")
}
current.PageNotFoundContent.Set(v)
}
if v, err := incoming.RedirectURL.Get(); err == nil {
+12
View File
@@ -171,6 +171,13 @@ func (m *Email) Create(
if err := email.Validate(); err != nil {
return nil, errs.Wrap(err)
}
// validate template content if present
if content, err := email.Content.Get(); err == nil {
if err := m.TemplateService.ValidateEmailTemplate(content.String()); err != nil {
m.Logger.Errorw("failed to validate email template", "error", err)
return nil, validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
}
}
// check uniqueness
var companyID *uuid.UUID
if cid, err := email.CompanyID.Get(); err == nil {
@@ -780,6 +787,11 @@ func (m *Email) UpdateByID(
current.MailHeaderSubject.Set(v)
}
if v, err := email.Content.Get(); err == nil {
// validate template content before updating
if err := m.TemplateService.ValidateEmailTemplate(v.String()); err != nil {
m.Logger.Errorw("failed to validate email template", "error", err)
return validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
}
if _, err := email.AddTrackingPixel.Get(); err == nil {
// handle tracking pixel
email, err = m.toggleTrackingPixel(email)
+13
View File
@@ -20,6 +20,7 @@ type Page struct {
PageRepository *repository.Page
CampaignRepository *repository.Campaign
CampaignTemplateService *CampaignTemplate
TemplateService *Template
}
// Create creates a new page
@@ -49,6 +50,13 @@ func (p *Page) Create(
p.Logger.Errorw("failed to validate page", "error", err)
return nil, errs.Wrap(err)
}
// validate template content if present
if content, err := page.Content.Get(); err == nil {
if err := p.TemplateService.ValidatePageTemplate(content.String()); err != nil {
p.Logger.Errorw("failed to validate page template", "error", err)
return nil, validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
}
}
// check uniqueness
name := page.Name.MustGet()
isOK, err := repository.CheckNameIsUnique(
@@ -253,6 +261,11 @@ func (p *Page) UpdateByID(
current.Name.Set(v)
}
if v, err := page.Content.Get(); err == nil {
// validate template content before updating
if err := p.TemplateService.ValidatePageTemplate(v.String()); err != nil {
p.Logger.Errorw("failed to validate page template", "error", err)
return validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content")
}
current.Content.Set(v)
}
// update page
+99
View File
@@ -18,6 +18,7 @@ import (
"github.com/phishingclub/phishingclub/errs"
"github.com/phishingclub/phishingclub/model"
"github.com/phishingclub/phishingclub/utils"
"github.com/phishingclub/phishingclub/vo"
"github.com/yeqown/go-qrcode/v2"
)
@@ -71,6 +72,104 @@ func (t *Template) CreateMail(
)
}
// ValidatePageTemplate validates that a page template can be parsed and executed without errors
func (t *Template) ValidatePageTemplate(content string) error {
// use the same parsing approach as CreatePhishingPage but without executing
_, err := template.New("validation").
Funcs(TemplateFuncs()).
Parse(content)
if err != nil {
return fmt.Errorf("failed to parse page template: %s", err)
}
// also try to execute with mock data to catch runtime errors
_, err = t.ApplyPageMock(content)
if err != nil {
return fmt.Errorf("failed to execute page template: %s", err)
}
return nil
}
// ValidateEmailTemplate validates that an email template can be parsed and executed without errors
func (t *Template) ValidateEmailTemplate(content string) error {
// use the same parsing approach as email creation but without executing
_, err := template.New("validation").
Funcs(TemplateFuncs()).
Parse(content)
if err != nil {
return fmt.Errorf("failed to parse email template: %s", err)
}
// also try to execute with mock data to catch runtime errors
domain := &model.Domain{
Name: nullable.NewNullableWithValue(
*vo.NewString255Must("example.test"),
),
}
recipient := model.NewRecipientExample()
campaignRecipient := model.CampaignRecipient{
ID: nullable.NewNullableWithValue(
uuid.New(),
),
Recipient: recipient,
}
email := model.NewEmailExample()
email.Content = nullable.NewNullableWithValue(
*vo.NewUnsafeOptionalString1MB(content),
)
apiSender := model.NewAPISenderExample()
_, err = t.CreateMailBody(
"id",
"/test",
domain,
&campaignRecipient,
email,
apiSender,
)
if err != nil {
return fmt.Errorf("failed to execute email template: %s", err)
}
return nil
}
// ValidateDomainTemplate validates that a domain template can be parsed and executed without errors
func (t *Template) ValidateDomainTemplate(content string) error {
// use the same parsing approach as domain content but without executing
_, err := template.New("validation").
Funcs(TemplateFuncs()).
Parse(content)
if err != nil {
return fmt.Errorf("failed to parse domain template: %s", err)
}
// also try to execute with mock data to catch runtime errors
// domains only have access to BaseURL variable
data := map[string]any{
"BaseURL": "https://example.test",
}
tmpl, err := template.New("domain").
Funcs(TemplateFuncs()).
Parse(content)
if err != nil {
return fmt.Errorf("failed to parse domain template: %s", err)
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
return fmt.Errorf("failed to execute domain template: %s", err)
}
return nil
}
// ApplyPageMock
func (t *Template) ApplyPageMock(content string) (*bytes.Buffer, error) {
// build response