From f9365ab29973e4612e70ffd3fe7612bcf8be450e Mon Sep 17 00:00:00 2001 From: Ronni Skansing Date: Tue, 30 Sep 2025 21:56:44 +0200 Subject: [PATCH] cleanup from old proxy page test Signed-off-by: Ronni Skansing --- backend/database/page.go | 15 +- backend/model/page.go | 64 +----- backend/repository/page.go | 34 +-- backend/service/page.go | 200 +---------------- frontend/src/lib/api/api.js | 6 +- .../src/lib/components/editor/Editor.svelte | 28 ++- frontend/src/routes/page/+page.svelte | 211 ++---------------- 7 files changed, 71 insertions(+), 487 deletions(-) diff --git a/backend/database/page.go b/backend/database/page.go index 77bae06..1f37cfe 100644 --- a/backend/database/page.go +++ b/backend/database/page.go @@ -13,15 +13,12 @@ const ( // Page is a gorm data model type Page struct { - ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"` - CreatedAt *time.Time `gorm:"not null;index;"` - UpdatedAt *time.Time `gorm:"not null;index"` - CompanyID *uuid.UUID `gorm:"index;uniqueIndex:idx_pages_unique_name_and_company_id;type:uuid"` - Name string `gorm:"not null;index;uniqueIndex:idx_pages_unique_name_and_company_id;"` - Content string `gorm:"not null;"` - Type string `gorm:"not null;default:'regular';"` - TargetURL string - ProxyConfig string + ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"` + CreatedAt *time.Time `gorm:"not null;index;"` + UpdatedAt *time.Time `gorm:"not null;index"` + CompanyID *uuid.UUID `gorm:"index;uniqueIndex:idx_pages_unique_name_and_company_id;type:uuid"` + Name string `gorm:"not null;index;uniqueIndex:idx_pages_unique_name_and_company_id;"` + Content string `gorm:"not null;"` // could has-one Company *Company diff --git a/backend/model/page.go b/backend/model/page.go index 1adedeb..6ed8a28 100644 --- a/backend/model/page.go +++ b/backend/model/page.go @@ -3,7 +3,6 @@ package model import ( "time" - "github.com/go-errors/errors" "github.com/google/uuid" "github.com/oapi-codegen/nullable" "github.com/phishingclub/phishingclub/validate" @@ -12,15 +11,12 @@ import ( // Page is a Page type Page struct { - ID nullable.Nullable[uuid.UUID] `json:"id"` - CreatedAt *time.Time `json:"createdAt"` - UpdatedAt *time.Time `json:"updatedAt"` - CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"` - Name nullable.Nullable[vo.String64] `json:"name"` - Content nullable.Nullable[vo.OptionalString1MB] `json:"content"` - Type nullable.Nullable[vo.String32] `json:"type"` // "regular" or "proxy" - TargetURL nullable.Nullable[vo.OptionalString1024] `json:"targetURL"` // target url for proxy pages - ProxyConfig nullable.Nullable[vo.OptionalString1MB] `json:"proxyConfig"` // yaml configuration for proxy + ID nullable.Nullable[uuid.UUID] `json:"id"` + CreatedAt *time.Time `json:"createdAt"` + UpdatedAt *time.Time `json:"updatedAt"` + CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"` + Name nullable.Nullable[vo.String64] `json:"name"` + Content nullable.Nullable[vo.OptionalString1MB] `json:"content"` Company *Company `json:"-"` } @@ -31,34 +27,8 @@ func (p *Page) Validate() error { return err } - // set default type if not specified - if !p.Type.IsSpecified() { - p.Type.Set(*vo.NewString32Must("regular")) - } - - pageType, err := p.Type.Get() - if err != nil { - return validate.WrapErrorWithField(errors.New("type is required"), "type") - } - - // validate type is either "regular" or "proxy" - if pageType.String() != "regular" && pageType.String() != "proxy" { - return validate.WrapErrorWithField(errors.New("type must be 'regular' or 'proxy'"), "type") - } - - if pageType.String() == "proxy" { - // proxy pages require targetURL and proxyConfig - if err := validate.NullableFieldRequired("targetURL", p.TargetURL); err != nil { - return err - } - if err := validate.NullableFieldRequired("proxyConfig", p.ProxyConfig); err != nil { - return err - } - } else { - // regular pages require content - if err := validate.NullableFieldRequired("content", p.Content); err != nil { - return err - } + if err := validate.NullableFieldRequired("content", p.Content); err != nil { + return err } return nil @@ -81,24 +51,6 @@ func (p *Page) ToDBMap() map[string]any { m["content"] = content.String() } } - if p.Type.IsSpecified() { - m["type"] = "regular" - if pageType, err := p.Type.Get(); err == nil { - m["type"] = pageType.String() - } - } - if p.TargetURL.IsSpecified() { - m["target_url"] = nil - if targetURL, err := p.TargetURL.Get(); err == nil { - m["target_url"] = targetURL.String() - } - } - if p.ProxyConfig.IsSpecified() { - m["proxy_config"] = nil - if proxyConfig, err := p.ProxyConfig.Get(); err == nil { - m["proxy_config"] = proxyConfig.String() - } - } if p.CompanyID.IsSpecified() { if p.CompanyID.IsNull() { m["company_id"] = nil diff --git a/backend/repository/page.go b/backend/repository/page.go index 9832975..11c45ca 100644 --- a/backend/repository/page.go +++ b/backend/repository/page.go @@ -254,34 +254,12 @@ func ToPage(row *database.Page) (*model.Page, error) { } content := nullable.NewNullableWithValue(*c) - // Handle proxy fields - typeValue := row.Type - if typeValue == "" { - typeValue = "regular" - } - pageType := nullable.NewNullableWithValue(*vo.NewString32Must(typeValue)) - - targetURL, err := vo.NewOptionalString1024(row.TargetURL) - if err != nil { - return nil, errs.Wrap(err) - } - targetURLNullable := nullable.NewNullableWithValue(*targetURL) - - proxyConfig, err := vo.NewOptionalString1MB(row.ProxyConfig) - if err != nil { - return nil, errs.Wrap(err) - } - proxyConfigNullable := nullable.NewNullableWithValue(*proxyConfig) - return &model.Page{ - ID: id, - CreatedAt: row.CreatedAt, - UpdatedAt: row.UpdatedAt, - CompanyID: companyID, - Name: name, - Content: content, - Type: pageType, - TargetURL: targetURLNullable, - ProxyConfig: proxyConfigNullable, + ID: id, + CreatedAt: row.CreatedAt, + UpdatedAt: row.UpdatedAt, + CompanyID: companyID, + Name: name, + Content: content, }, nil } diff --git a/backend/service/page.go b/backend/service/page.go index 0464a1c..76056ab 100644 --- a/backend/service/page.go +++ b/backend/service/page.go @@ -2,12 +2,8 @@ package service import ( "context" - "net/url" - "regexp" - "strings" "github.com/go-errors/errors" - "gopkg.in/yaml.v3" "github.com/google/uuid" "github.com/phishingclub/phishingclub/data" @@ -15,7 +11,6 @@ import ( "github.com/phishingclub/phishingclub/model" "github.com/phishingclub/phishingclub/repository" "github.com/phishingclub/phishingclub/validate" - "github.com/phishingclub/phishingclub/vo" "gorm.io/gorm" ) @@ -29,39 +24,6 @@ type Page struct { DomainRepository *repository.Domain } -// ProxyConfig represents the YAML configuration for proxy pages -type ProxyConfig struct { - Default map[string]interface{} `yaml:"default,omitempty"` - Hosts map[string]ProxyHostConfig `yaml:",inline"` -} - -// ProxyHostConfig represents configuration for a specific host -type ProxyHostConfig struct { - Proxy string `yaml:"proxy,omitempty"` - Domain string `yaml:"domain,omitempty"` - Capture []ProxyCaptureRule `yaml:"capture,omitempty"` - Replace []ProxyReplaceRule `yaml:"replace,omitempty"` -} - -// ProxyCaptureRule represents a capture rule -type ProxyCaptureRule struct { - Name string `yaml:"name"` - Method string `yaml:"method,omitempty"` - Path string `yaml:"path,omitempty"` - Pattern string `yaml:"pattern,omitempty"` - Find string `yaml:"find"` - From string `yaml:"from,omitempty"` - Required *bool `yaml:"required,omitempty"` -} - -// ProxyReplaceRule represents a replace rule -type ProxyReplaceRule struct { - Name string `yaml:"name"` - Find string `yaml:"find"` - Replace string `yaml:"replace"` - From string `yaml:"from,omitempty"` -} - // Create creates a new page func (p *Page) Create( ctx context.Context, @@ -89,20 +51,11 @@ func (p *Page) Create( p.Logger.Errorw("failed to validate page", "error", err) return nil, errs.Wrap(err) } - // validate based on page type - pageType, _ := page.Type.Get() - if pageType.String() == "proxy" { - // validate proxy configuration - if err := p.validateProxyPage(ctx, page); err != nil { - return nil, err - } - } else { - // validate template content for regular pages - 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") - } + // validate template content + 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 @@ -308,33 +261,15 @@ func (p *Page) UpdateByID( } current.Name.Set(v) } - if v, err := page.Type.Get(); err == nil { - current.Type.Set(v) - } - if v, err := page.TargetURL.Get(); err == nil { - current.TargetURL.Set(v) - } - if v, err := page.ProxyConfig.Get(); err == nil { - current.ProxyConfig.Set(v) - } if v, err := page.Content.Get(); err == nil { current.Content.Set(v) } - // validate based on updated page type - updatedPageType, _ := current.Type.Get() - if updatedPageType.String() == "proxy" { - // validate proxy configuration - if err := p.validateProxyPage(ctx, current); err != nil { - return err - } - } else { - // validate template content for regular pages - if content, err := current.Content.Get(); err == nil { - if err := p.TemplateService.ValidatePageTemplate(content.String()); err != nil { - p.Logger.Errorw("failed to validate page template", "error", err) - return validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content") - } + // validate template content + if content, err := current.Content.Get(); err == nil { + if err := p.TemplateService.ValidatePageTemplate(content.String()); err != nil { + p.Logger.Errorw("failed to validate page template", "error", err) + return validate.WrapErrorWithField(errors.New("invalid template: "+err.Error()), "content") } } // update page @@ -353,121 +288,6 @@ func (p *Page) UpdateByID( return nil } -// validateProxyPage validates proxy page configuration -func (p *Page) validateProxyPage(ctx context.Context, page *model.Page) error { - // validate target URL format - targetURL, err := page.TargetURL.Get() - if err != nil { - return validate.WrapErrorWithField(errors.New("target URL is required for proxy pages"), "targetURL") - } - - parsedURL, err := url.Parse(targetURL.String()) - if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" { - return validate.WrapErrorWithField(errors.New("invalid target URL format - must be a valid HTTP or HTTPS URL"), "targetURL") - } - - if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { - return validate.WrapErrorWithField(errors.New("target URL must use HTTP or HTTPS protocol"), "targetURL") - } - - // validate proxy configuration YAML - proxyConfig, err := page.ProxyConfig.Get() - if err != nil { - return validate.WrapErrorWithField(errors.New("proxy configuration is required for proxy pages"), "proxyConfig") - } - - var config ProxyConfig - if err := yaml.Unmarshal([]byte(proxyConfig.String()), &config); err != nil { - return validate.WrapErrorWithField(errors.New("invalid YAML format: "+err.Error()), "proxyConfig") - } - - // validate that all referenced domains in the config support proxy - for hostname, hostConfig := range config.Hosts { - if hostConfig.Domain != "" { - domainName, err := vo.NewString255(hostConfig.Domain) - if err != nil { - return validate.WrapErrorWithField( - errors.New("invalid domain name format"), - "proxyConfig", - ) - } - - _, err = p.DomainRepository.GetByName(ctx, domainName, &repository.DomainOption{}) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return validate.WrapErrorWithField( - errors.New("referenced domain '"+hostConfig.Domain+"' not found"), - "proxyConfig", - ) - } - return err - } - } - - // validate capture rules - for _, capture := range hostConfig.Capture { - if capture.Name == "" { - return validate.WrapErrorWithField(errors.New("capture rule name is required"), "proxyConfig") - } - if capture.Pattern == "" && capture.Path == "" { - return validate.WrapErrorWithField( - errors.New("capture rule must have either pattern or path"), - "proxyConfig", - ) - } - if capture.Pattern != "" { - if _, err := regexp.Compile(capture.Pattern); err != nil { - return validate.WrapErrorWithField( - errors.New("invalid regex pattern in capture rule: "+err.Error()), - "proxyConfig", - ) - } - } - if capture.Path != "" { - if _, err := regexp.Compile(capture.Path); err != nil { - return validate.WrapErrorWithField( - errors.New("invalid regex pattern for path in capture rule: "+err.Error()), - "proxyConfig", - ) - } - } - if capture.From != "" { - validFromValues := []string{"request_body", "request_header", "response_body", "response_header", "any"} - valid := false - for _, validFrom := range validFromValues { - if capture.From == validFrom { - valid = true - break - } - } - if !valid { - return validate.WrapErrorWithField( - errors.New("invalid 'from' value in capture rule, must be one of: "+strings.Join(validFromValues, ", ")), - "proxyConfig", - ) - } - } - } - - // validate replace rules - for _, replace := range hostConfig.Replace { - if replace.Find == "" { - return validate.WrapErrorWithField(errors.New("replace rule 'find' is required"), "proxyConfig") - } - if _, err := regexp.Compile(replace.Find); err != nil { - return validate.WrapErrorWithField( - errors.New("invalid regex pattern in replace rule 'find': "+err.Error()), - "proxyConfig", - ) - } - } - - p.Logger.Debugw("validated proxy host config", "hostname", hostname) - } - - return nil -} - // DeleteByID deletes a page by ID func (p *Page) DeleteByID( ctx context.Context, diff --git a/frontend/src/lib/api/api.js b/frontend/src/lib/api/api.js index 504fe62..d139a7f 100644 --- a/frontend/src/lib/api/api.js +++ b/frontend/src/lib/api/api.js @@ -1314,15 +1314,13 @@ export class API { * @param {string} name * @param {string} content * @param {string} companyID - * @param {object} additionalFields - Optional additional fields for Proxy pages * @returns {Promise} */ - create: async (name, content, companyID, additionalFields = {}) => { + create: async (name, content, companyID) => { const payload = { name: name, content: content, - companyID: companyID, - ...additionalFields + companyID: companyID }; return await postJSON(this.getPath('/page'), payload); }, diff --git a/frontend/src/lib/components/editor/Editor.svelte b/frontend/src/lib/components/editor/Editor.svelte index fd42092..6c159b3 100644 --- a/frontend/src/lib/components/editor/Editor.svelte +++ b/frontend/src/lib/components/editor/Editor.svelte @@ -388,11 +388,19 @@ isDetailsVisible = true; }} type="button" - class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md w-36 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 mb-2 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 transition-colors duration-200" + class="h-8 border-2 rounded-md w-36 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 mb-2 transition-colors duration-200" class:font-bold={isDetailsVisible} - class:bg-cta-blue={isDetailsVisible} - class:dark:bg-indigo-600={isDetailsVisible} + class:bg-blue-600={isDetailsVisible} + class:dark:bg-blue-500={isDetailsVisible} class:text-white={isDetailsVisible} + class:border-blue-600={isDetailsVisible} + class:dark:border-blue-500={isDetailsVisible} + class:text-gray-700={!isDetailsVisible} + class:dark:text-gray-200={!isDetailsVisible} + class:bg-white={!isDetailsVisible} + class:dark:bg-gray-700={!isDetailsVisible} + class:border-gray-300={!isDetailsVisible} + class:dark:border-gray-600={!isDetailsVisible} > { try { - const pageData = { - name: formValues.name, - type: formValues.type, - content: isRegularPage ? formValues.content : null, - targetURL: isProxyPage ? formValues.targetURL : null, - proxyConfig: isProxyPage ? formValues.proxyConfig : null - }; - - const res = await api.page.create( - pageData.name, - pageData.content, - contextCompanyID, - pageData - ); + const res = await api.page.create(formValues.name, formValues.content, contextCompanyID); if (!res.success) { formError = res.error; return; @@ -240,10 +175,7 @@ replace: try { const updateData = { name: formValues.name, - type: formValues.type, - content: isRegularPage ? formValues.content : null, - targetURL: isProxyPage ? formValues.targetURL : null, - proxyConfig: isProxyPage ? formValues.proxyConfig : null + content: formValues.content }; const res = await api.page.update(formValues.id, updateData); @@ -288,9 +220,6 @@ replace: formValues.content = ''; formValues.name = ''; formValues.id = ''; - formValues.type = 'regular'; - formValues.targetURL = ''; - formValues.proxyConfig = ''; form.reset(); formError = ''; }; @@ -305,10 +234,7 @@ replace: formValues = { id: null, name: null, - content: null, - type: 'regular', - targetURL: null, - proxyConfig: null + content: null }; try { @@ -337,10 +263,7 @@ replace: formValues = { id: null, name: null, - content: null, - type: 'regular', - targetURL: null, - proxyConfig: null + content: null }; try { @@ -366,9 +289,6 @@ replace: formValues.id = page.id; formValues.name = page.name; formValues.content = page.content || ''; - formValues.type = page.type && page.type.trim() !== '' ? page.type : 'regular'; - formValues.targetURL = page.targetURL || ''; - formValues.proxyConfig = page.proxyConfig || ''; }; /** @param {*} event */ @@ -440,115 +360,18 @@ replace: -
- -
-

- Basic Information -

-
-
- Name -
-
-
-
-
-

- Type -

-
-
- - -
-
-
-
-
+ +
+ Name
- - -
-

- {#if isProxyPage} - Proxy Configuration - {:else} - Page Content - {/if} -

- - {#if isRegularPage} - - {/if} - - {#if isProxyPage} -
-
-
-

- Proxy Capture & Replacement Rules (YAML) -

-
- Data captures require a 'find' pattern. Path-based navigation tracking (any - method) doesn't need 'find'. All captures are required by default. -
-
-
- -
-
-
- {/if} -
- - -
- + +