package model import ( "time" "github.com/go-errors/errors" "github.com/google/uuid" "github.com/oapi-codegen/nullable" "github.com/phishingclub/phishingclub/errs" "github.com/phishingclub/phishingclub/validate" "github.com/phishingclub/phishingclub/vo" ) type Domain struct { ID nullable.Nullable[uuid.UUID] `json:"id"` CreatedAt *time.Time `json:"createdAt"` UpdatedAt *time.Time `json:"updatedAt"` Name nullable.Nullable[vo.String255] `json:"name"` Type nullable.Nullable[vo.String32] `json:"type"` // "regular" or "proxy" ProxyTargetDomain nullable.Nullable[vo.OptionalString255] `json:"proxyTargetDomain"` // target URL for proxy (can be full URL or domain) HostWebsite nullable.Nullable[bool] `json:"hostWebsite"` ManagedTLS nullable.Nullable[bool] `json:"managedTLS"` OwnManagedTLS nullable.Nullable[bool] `json:"ownManagedTLS"` SelfSignedTLS nullable.Nullable[bool] `json:"selfSignedTLS"` // private key OwnManagedTLSKey nullable.Nullable[string] `json:"ownManagedTLSKey"` // cert OwnManagedTLSPem nullable.Nullable[string] `json:"ownManagedTLSPem"` PageContent nullable.Nullable[vo.OptionalString1MB] `json:"pageContent"` PageNotFoundContent nullable.Nullable[vo.OptionalString1MB] `json:"pageNotFoundContent"` RedirectURL nullable.Nullable[vo.OptionalString1024] `json:"redirectURL"` CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"` ProxyID nullable.Nullable[uuid.UUID] `json:"proxyID"` Company *Company `json:"company"` } // Validate checks if the Domain configuration with a valid state func (d *Domain) Validate() error { if err := validate.NullableFieldRequired("name", d.Name); err != nil { return err } // set default type if not specified if !d.Type.IsSpecified() { d.Type.Set(*vo.NewString32Must("regular")) } domainType, err := d.Type.Get() if err != nil { return validate.WrapErrorWithField(errors.New("type is required"), "type") } // validate type is either "regular" or "proxy" if domainType.String() != "regular" && domainType.String() != "proxy" { return validate.WrapErrorWithField(errors.New("type must be 'regular' or 'proxy'"), "type") } if domainType.String() == "proxy" { // proxy domains require proxyTargetDomain if err := validate.NullableFieldRequired("proxyTargetDomain", d.ProxyTargetDomain); err != nil { return err } // proxy domains don't need page content validation } else { // regular domains need standard validation if err := validate.NullableFieldRequired("hostWebsite", d.HostWebsite); err != nil { return err } if err := validate.NullableFieldRequired("managedTLS", d.ManagedTLS); err != nil { return err } if err := validate.NullableFieldRequired("pageContent", d.PageContent); err != nil { return err } if err := validate.NullableFieldRequired("pageNotFoundContent", d.PageNotFoundContent); err != nil { return err } if err := validate.NullableFieldRequired("redirectURL", d.RedirectURL); err != nil { return err } } // // ownManagedTLS, err := d.OwnManagedTLS.Get() ownManagedTLSSet := err == nil && ownManagedTLS selfSignedTLS, err := d.SelfSignedTLS.Get() selfSignedTLSSet := err == nil && selfSignedTLS managedTLS, err := d.ManagedTLS.Get() managedTLSSet := err == nil && managedTLS // count how many TLS options are enabled tlsCount := 0 if managedTLSSet { tlsCount++ } if ownManagedTLSSet { tlsCount++ } if selfSignedTLSSet { tlsCount++ } // only one TLS option can be enabled at a time if tlsCount > 1 { return errs.NewValidationError(errors.New( "Domain TLS can only have one option enabled: managed, own managed, or self-signed", )) } if ownManagedTLS { // handle own managed ManagedTLS ownManagedTLSKey, err := d.OwnManagedTLSKey.Get() ownManagedTLSPem, err := d.OwnManagedTLSPem.Get() ownManagedTLSKeyIsSet := err == nil && len(ownManagedTLSKey) > 0 ownManagedTLSPemIsSet := err == nil && len(ownManagedTLSPem) > 0 // both must be set, not one of if (ownManagedTLSKeyIsSet && !ownManagedTLSPemIsSet) || (!ownManagedTLSKeyIsSet && ownManagedTLSPemIsSet) { return errs.NewValidationError(errors.New( "Own managed TLS requires a private key (.key) and a certificate (.pem)", )) } } /* // TODO hostWebsite vs redirectURL are mutually exclusive hostWebsite := d.HostWebsite.MustGet() redirectURL := d.RedirectURL.MustGet() redirectURLLen := len(redirectURL.String()) if hostWebsite && redirectURLLen > 0 { return validate.WrapErrorWithField( errors.New("both can not be set"), "Host website and redirect url", ) } */ return nil } // ToDBMap converts the fields that can be stored or updated to a map // if the value is nullable and not set, it is not included // if the value is nullable and set, it is included, if it is null, it is set to nil func (d *Domain) ToDBMap() map[string]any { m := map[string]any{} if d.Name.IsSpecified() { m["name"] = nil if name, err := d.Name.Get(); err == nil { m["name"] = name.String() } } if d.Type.IsSpecified() { m["type"] = "regular" if domainType, err := d.Type.Get(); err == nil { m["type"] = domainType.String() } } if d.ProxyTargetDomain.IsSpecified() { m["proxy_target_domain"] = nil if proxyTargetDomain, err := d.ProxyTargetDomain.Get(); err == nil { m["proxy_target_domain"] = proxyTargetDomain.String() } } if d.HostWebsite.IsSpecified() { m["host_website"] = nil if hostWebsite, err := d.HostWebsite.Get(); err == nil { m["host_website"] = hostWebsite } m["redirect_url"] = "" } if d.RedirectURL.IsSpecified() { m["redirect_url"] = nil if redirectURL, err := d.RedirectURL.Get(); err == nil { m["redirect_url"] = redirectURL.String() } } if d.PageContent.IsSpecified() { m["page_content"] = nil if staticPage, err := d.PageContent.Get(); err == nil { m["page_content"] = staticPage.String() } } if d.PageNotFoundContent.IsSpecified() { m["page_not_found_content"] = nil if staticNotFound, err := d.PageNotFoundContent.Get(); err == nil { m["page_not_found_content"] = staticNotFound.String() } } if d.CompanyID.IsSpecified() { if d.CompanyID.IsNull() { m["company_id"] = nil } else { m["company_id"] = d.CompanyID.MustGet() } } if d.ManagedTLS.IsSpecified() { m["managed_tls_certs"] = false if d.ManagedTLS.IsNull() { m["managed_tls_certs"] = nil } else { m["managed_tls_certs"] = d.ManagedTLS.MustGet() } } if d.OwnManagedTLS.IsSpecified() { m["own_managed_tls"] = false if d.OwnManagedTLS.IsNull() { m["own_managed_tls"] = nil } else { m["own_managed_tls"] = d.OwnManagedTLS.MustGet() } } if d.SelfSignedTLS.IsSpecified() { m["self_signed_tls"] = false if d.SelfSignedTLS.IsNull() { m["self_signed_tls"] = nil } else { m["self_signed_tls"] = d.SelfSignedTLS.MustGet() } } if d.ProxyID.IsSpecified() { if d.ProxyID.IsNull() { m["proxy_id"] = nil } else { m["proxy_id"] = d.ProxyID.MustGet() } } return m } // DomainOverview is a subset of the domain as used as read-only type DomainOverview struct { ID uuid.UUID `json:"id,omitempty"` CreatedAt *time.Time `json:"createdAt"` UpdatedAt *time.Time `json:"updatedAt"` Name string `json:"name"` Type string `json:"type"` ProxyTargetDomain string `json:"proxyTargetDomain"` HostWebsite bool `json:"hostWebsite"` ManagedTLS bool `json:"managedTLS"` OwnManagedTLS bool `json:"ownManagedTLS"` SelfSignedTLS bool `json:"selfSignedTLS"` RedirectURL string `json:"redirectURL"` CompanyID *uuid.UUID `json:"companyID"` ProxyID *uuid.UUID `json:"proxyID"` }