mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-12 01:58:53 +02:00
Initial open source release
This commit is contained in:
commit
11cf01f08e
488 files changed
+97180
No files matched your search
@@ -0,0 +1,19 @@
|
||||
# Description
|
||||
The `model` folder represents the core data structures. It includes:
|
||||
|
||||
- Factories for creating and initializing entities
|
||||
- Type definitions for all domain objects
|
||||
- Validation logic to ensure data integrity
|
||||
- Custom marshalling/unmarshalling for JSON handling
|
||||
- Request/Response structures for API endpoints
|
||||
- Helper methods for data transformation
|
||||
- Business rules and constraints
|
||||
- Data transfer objects (DTOs) for external communication
|
||||
|
||||
The members mainly use `nullable.Nullable` to convey if a field is set so we know
|
||||
if it should be updated etc.
|
||||
|
||||
The structures in this folder serve as the contract between the API layer and the database layer, handling all necessary data transformations and validations before persistence or transmission.
|
||||
|
||||
Files affixed with `View` are read-only models meant only for outgoing API data,
|
||||
this could be joined data from a database.
|
||||
@@ -0,0 +1,119 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"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"
|
||||
)
|
||||
|
||||
// AllowDeny is a model for allow deny listing
|
||||
type AllowDeny struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String127] `json:"name"`
|
||||
Cidrs nullable.Nullable[vo.IPNetSlice] `json:"cidrs"`
|
||||
Allowed nullable.Nullable[bool] `json:"allowed"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
}
|
||||
|
||||
// Validate checks if the allow deny list has a valid state
|
||||
func (r *AllowDeny) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", r.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("cidrs", r.Cidrs); err != nil {
|
||||
return err
|
||||
}
|
||||
if v := r.Cidrs.MustGet(); len(v) == 0 {
|
||||
return errs.NewValidationError(
|
||||
errors.New("cidrs must include atleast one CIDR"),
|
||||
)
|
||||
}
|
||||
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 (r *AllowDeny) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if r.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := r.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if r.Cidrs.IsSpecified() {
|
||||
m["cidrs"] = nil
|
||||
if cidrs, err := r.Cidrs.Get(); err == nil {
|
||||
cidrsStr := ""
|
||||
cidrsLen := len(cidrs)
|
||||
for i, cidr := range cidrs {
|
||||
if i == cidrsLen {
|
||||
cidrsStr += fmt.Sprintf("%s", cidr.String())
|
||||
|
||||
} else {
|
||||
cidrsStr += fmt.Sprintf("%s\n", cidr.String())
|
||||
}
|
||||
}
|
||||
m["cidrs"] = cidrsStr
|
||||
}
|
||||
}
|
||||
if r.Allowed.IsSpecified() {
|
||||
m["allowed"] = nil
|
||||
if allowed, err := r.Allowed.Get(); err == nil {
|
||||
m["allowed"] = allowed
|
||||
}
|
||||
}
|
||||
if r.CompanyID.IsSpecified() {
|
||||
if r.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = r.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *AllowDeny) IsIPAllowed(ip string) (bool, error) {
|
||||
isTypeAllowList := r.Allowed.MustGet()
|
||||
cidrs, err := r.Cidrs.Get()
|
||||
if err != nil {
|
||||
return false, errs.Wrap(err)
|
||||
}
|
||||
|
||||
netIP := net.ParseIP(ip)
|
||||
if netIP == nil {
|
||||
return false, fmt.Errorf("invalid ip address: %s", ip)
|
||||
}
|
||||
|
||||
for _, cidr := range cidrs {
|
||||
isInRange := cidr.Contains(netIP)
|
||||
// if allow list and ip is within range
|
||||
if isTypeAllowList && isInRange {
|
||||
return true, nil
|
||||
}
|
||||
// if deny list and ip is within range
|
||||
if !isTypeAllowList && isInRange {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If this is an allow list and we didn't find the IP, it's not allowed
|
||||
if isTypeAllowList {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If this is a deny list and we didn't find the IP, it is allowed
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// APISender is a API sender
|
||||
type APISender 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"`
|
||||
APIKey nullable.Nullable[vo.OptionalString255] `json:"apiKey"`
|
||||
CustomField1 nullable.Nullable[vo.OptionalString255] `json:"customField1"`
|
||||
CustomField2 nullable.Nullable[vo.OptionalString255] `json:"customField2"`
|
||||
CustomField3 nullable.Nullable[vo.OptionalString255] `json:"customField3"`
|
||||
CustomField4 nullable.Nullable[vo.OptionalString255] `json:"customField4"`
|
||||
RequestMethod nullable.Nullable[vo.HTTPMethod] `json:"requestMethod"`
|
||||
RequestURL nullable.Nullable[vo.String255] `json:"requestURL"`
|
||||
RequestHeaders nullable.Nullable[APISenderHeaders] `json:"requestHeaders"`
|
||||
RequestBody nullable.Nullable[vo.OptionalString1MB] `json:"requestBody"`
|
||||
ExpectedResponseStatusCode nullable.Nullable[int] `json:"expectedResponseStatusCode"`
|
||||
ExpectedResponseHeaders nullable.Nullable[APISenderHeaders] `json:"expectedResponseHeaders"`
|
||||
ExpectedResponseBody nullable.Nullable[vo.OptionalString1MB] `json:"expectedResponseBody"`
|
||||
}
|
||||
|
||||
// Validate checks if the API sender has a valid state
|
||||
func (a *APISender) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", a.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("requestMethod", a.RequestMethod); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("requestURL", a.RequestURL); err != nil {
|
||||
return err
|
||||
}
|
||||
// one of the following is required
|
||||
if (!a.ExpectedResponseStatusCode.IsSpecified() || a.ExpectedResponseStatusCode.IsNull()) &&
|
||||
!isSpecifiedStringWithContent(a.ExpectedResponseBody) &&
|
||||
!isSpecifiedStringWithContent(a.ExpectedResponseHeaders) {
|
||||
return validate.WrapErrorWithField(errors.New("expectedResponseStatusCode, expectedResponseBody or expectedResponseHeaders must be supplied"), "Missing field")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSpecifiedStringWithContent[T fmt.Stringer](s nullable.Nullable[T]) bool {
|
||||
return s.IsSpecified() && !s.IsNull() && s.MustGet().String() != ""
|
||||
}
|
||||
|
||||
// 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 (a *APISender) ToDBMap() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
if a.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := a.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if a.CompanyID.IsSpecified() {
|
||||
if a.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = a.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
if a.APIKey.IsSpecified() {
|
||||
m["api_key"] = nil
|
||||
if apiKey, err := a.APIKey.Get(); err == nil {
|
||||
m["api_key"] = apiKey.String()
|
||||
}
|
||||
}
|
||||
if a.CustomField1.IsSpecified() {
|
||||
m["custom_field1"] = nil
|
||||
if customField1, err := a.CustomField1.Get(); err == nil {
|
||||
m["custom_field1"] = customField1.String()
|
||||
}
|
||||
}
|
||||
if a.CustomField2.IsSpecified() {
|
||||
m["custom_field2"] = nil
|
||||
if customField2, err := a.CustomField2.Get(); err == nil {
|
||||
m["custom_field2"] = customField2.String()
|
||||
}
|
||||
}
|
||||
if a.CustomField3.IsSpecified() {
|
||||
m["custom_field3"] = nil
|
||||
if customField3, err := a.CustomField3.Get(); err == nil {
|
||||
m["custom_field3"] = customField3.String()
|
||||
}
|
||||
}
|
||||
if a.CustomField4.IsSpecified() {
|
||||
m["custom_field4"] = nil
|
||||
if customField4, err := a.CustomField4.Get(); err == nil {
|
||||
m["custom_field4"] = customField4.String()
|
||||
}
|
||||
}
|
||||
if a.RequestMethod.IsSpecified() {
|
||||
m["request_method"] = nil
|
||||
if requestMethod, err := a.RequestMethod.Get(); err == nil {
|
||||
m["request_method"] = requestMethod.String()
|
||||
}
|
||||
}
|
||||
if a.RequestURL.IsSpecified() {
|
||||
m["request_url"] = nil
|
||||
if requestURL, err := a.RequestURL.Get(); err == nil {
|
||||
m["request_url"] = requestURL.String()
|
||||
}
|
||||
}
|
||||
if a.RequestHeaders.IsSpecified() {
|
||||
m["request_headers"] = nil
|
||||
if requestHeaders, err := a.RequestHeaders.Get(); err == nil {
|
||||
m["request_headers"] = requestHeaders.String()
|
||||
}
|
||||
}
|
||||
if a.RequestBody.IsSpecified() {
|
||||
m["request_body"] = nil
|
||||
if requestBody, err := a.RequestBody.Get(); err == nil {
|
||||
m["request_body"] = requestBody.String()
|
||||
}
|
||||
}
|
||||
if a.ExpectedResponseStatusCode.IsSpecified() {
|
||||
m["expected_response_status_code"] = nil
|
||||
if expectedResponseStatusCode, err := a.ExpectedResponseStatusCode.Get(); err == nil {
|
||||
m["expected_response_status_code"] = expectedResponseStatusCode
|
||||
}
|
||||
}
|
||||
if a.ExpectedResponseHeaders.IsSpecified() {
|
||||
m["expected_response_headers"] = nil
|
||||
if expectedResponseHeaders, err := a.ExpectedResponseHeaders.Get(); err == nil {
|
||||
m["expected_response_headers"] = expectedResponseHeaders.String()
|
||||
}
|
||||
}
|
||||
if a.ExpectedResponseBody.IsSpecified() {
|
||||
m["expected_response_body"] = nil
|
||||
if expectedResponseBody, err := a.ExpectedResponseBody.Get(); err == nil {
|
||||
m["expected_response_body"] = expectedResponseBody.String()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// TODO should the rest of the code be moved to value object
|
||||
type HTTPHeader struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// APISenderHeaders is a header for a API sender
|
||||
type APISenderHeaders struct {
|
||||
Headers []*HTTPHeader
|
||||
}
|
||||
|
||||
// NewAPISenderHeader creates a new APISenderHeader
|
||||
// it takes a newline separated string with headers of the format .+: .+ (key: value)
|
||||
func NewAPISenderHeader(headers string) (*APISenderHeaders, error) {
|
||||
headers = strings.TrimSpace(headers)
|
||||
if headers == "" {
|
||||
return &APISenderHeaders{}, nil
|
||||
}
|
||||
// split the headers
|
||||
lines := strings.Split(headers, "\n")
|
||||
// if there is a single header
|
||||
if len(lines) == 0 {
|
||||
return &APISenderHeaders{
|
||||
Headers: []*HTTPHeader{},
|
||||
}, nil
|
||||
}
|
||||
headerLines := []*HTTPHeader{}
|
||||
for _, line := range lines {
|
||||
// split the key value
|
||||
parts := strings.Split(line, ":")
|
||||
// there should be atleast 2 parts, key and value
|
||||
if len(parts) < 2 {
|
||||
return nil, validate.WrapErrorWithField(
|
||||
fmt.Errorf("invalid header: %s", line),
|
||||
"header",
|
||||
)
|
||||
}
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(strings.Join(parts[1:], ":"))
|
||||
headerLines = append(headerLines, &HTTPHeader{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
return &APISenderHeaders{
|
||||
Headers: headerLines,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface
|
||||
func (s APISenderHeaders) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals the json into a string
|
||||
func (s *APISenderHeaders) UnmarshalJSON(data []byte) error {
|
||||
var header string
|
||||
if err := json.Unmarshal(data, &header); err != nil {
|
||||
return err
|
||||
}
|
||||
ss, err := NewAPISenderHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Headers = ss.Headers
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the APISenderHeader
|
||||
func (a APISenderHeaders) String() string {
|
||||
headers := ""
|
||||
for _, header := range a.Headers {
|
||||
headers += header.Key + ": " + header.Value + "\n"
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func NewAPISenderExample() *APISender {
|
||||
apiSenderRequestHeaders, err := NewAPISenderHeader("foo: bar")
|
||||
if err != nil {
|
||||
panic("APISender example data MUST be valid")
|
||||
}
|
||||
return &APISender{
|
||||
Name: nullable.NewNullableWithValue(
|
||||
*vo.NewString64Must("Example"),
|
||||
),
|
||||
APIKey: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must(
|
||||
"rj90jf09jr09j2r",
|
||||
),
|
||||
),
|
||||
CustomField1: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must(
|
||||
"custom1",
|
||||
),
|
||||
),
|
||||
CustomField2: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must(
|
||||
"custom2",
|
||||
),
|
||||
),
|
||||
CustomField3: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must(
|
||||
"custom3",
|
||||
),
|
||||
),
|
||||
CustomField4: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must(
|
||||
"custom4",
|
||||
),
|
||||
),
|
||||
RequestMethod: nullable.NewNullableWithValue(
|
||||
*vo.NewHTTPMethodMust("POST"),
|
||||
),
|
||||
RequestURL: nullable.NewNullableWithValue(
|
||||
*vo.NewString255Must("https://example.com"),
|
||||
),
|
||||
RequestHeaders: nullable.NewNullableWithValue(
|
||||
*apiSenderRequestHeaders,
|
||||
),
|
||||
RequestBody: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString1MBMust("<p>Hello World</p>"),
|
||||
),
|
||||
ExpectedResponseStatusCode: nullable.NewNullableWithValue(200),
|
||||
ExpectedResponseHeaders: nullable.NewNullableWithValue(
|
||||
*apiSenderRequestHeaders,
|
||||
),
|
||||
ExpectedResponseBody: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString1MBMust("<p>World</p>"),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Asset is a file Asset entity
|
||||
type Asset 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"`
|
||||
DomainName nullable.Nullable[vo.String255] `json:"domainName"`
|
||||
DomainID nullable.Nullable[uuid.UUID] `json:"domainID"`
|
||||
Name nullable.Nullable[vo.OptionalString127] `json:"name"`
|
||||
Description nullable.Nullable[vo.OptionalString255] `json:"description"`
|
||||
Path nullable.Nullable[vo.RelativeFilePath] `json:"path"`
|
||||
File multipart.FileHeader `json:"-"`
|
||||
}
|
||||
|
||||
// Validate checks if the Asset has a valid state
|
||||
func (a *Asset) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", a.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (a *Asset) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if a.CompanyID.IsSpecified() {
|
||||
if a.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = a.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
if a.DomainName.IsSpecified() {
|
||||
m["domain_name"] = nil
|
||||
if domainName, err := a.DomainName.Get(); err == nil {
|
||||
m["domain_name"] = domainName.String()
|
||||
}
|
||||
}
|
||||
// TODO is a global asset attached to a domain? if not then this should be possible to set to null like company ID
|
||||
if a.DomainID.IsSpecified() {
|
||||
m["domain_id"] = nil
|
||||
if domainID, err := a.DomainID.Get(); err == nil {
|
||||
m["domain_id"] = domainID.String()
|
||||
}
|
||||
}
|
||||
if a.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := a.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if a.Description.IsSpecified() {
|
||||
m["description"] = nil
|
||||
if description, err := a.Description.Get(); err == nil {
|
||||
m["description"] = description.String()
|
||||
}
|
||||
}
|
||||
if a.Path.IsSpecified() {
|
||||
m["path"] = nil
|
||||
if path, err := a.Path.Get(); err == nil {
|
||||
m["path"] = path.String()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
type Attachment 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.OptionalString127] `json:"name"`
|
||||
Description nullable.Nullable[vo.OptionalString255] `json:"description"`
|
||||
FileName nullable.Nullable[vo.FileName] `json:"fileName"`
|
||||
EmbeddedContent nullable.Nullable[bool] `json:"embeddedContent"`
|
||||
// path is the calculated path to the file
|
||||
Path nullable.Nullable[vo.RelativeFilePath] `json:"path"`
|
||||
// used in the API to upload files
|
||||
File *multipart.FileHeader `json:"-"`
|
||||
}
|
||||
|
||||
func (a *Attachment) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", a.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Attachment) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if a.CompanyID.IsSpecified() {
|
||||
if a.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = a.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
if a.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := a.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if a.Description.IsSpecified() {
|
||||
m["description"] = nil
|
||||
if description, err := a.Description.Get(); err == nil {
|
||||
m["description"] = description.String()
|
||||
}
|
||||
}
|
||||
if a.FileName.IsSpecified() {
|
||||
m["filename"] = nil
|
||||
if fileName, err := a.FileName.Get(); err == nil {
|
||||
m["filename"] = fileName.String()
|
||||
}
|
||||
// if name is not set, use file name
|
||||
if m["name"] == nil {
|
||||
m["name"] = m["filename"]
|
||||
}
|
||||
}
|
||||
if a.Path.IsSpecified() {
|
||||
m["path"] = nil
|
||||
if path, err := a.Path.Get(); err == nil {
|
||||
m["path"] = path.String()
|
||||
}
|
||||
}
|
||||
if v, err := a.EmbeddedContent.Get(); err == nil {
|
||||
m["embedded_content"] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/utils"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Campaign is a phishing Campaign entity
|
||||
type Campaign struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
CloseAt nullable.Nullable[time.Time] `json:"closeAt"`
|
||||
ClosedAt nullable.Nullable[time.Time] `json:"closedAt"`
|
||||
AnonymizeAt nullable.Nullable[time.Time] `json:"anonymizeAt"`
|
||||
AnonymizedAt nullable.Nullable[time.Time] `json:"anonymizedAt"`
|
||||
SortField nullable.Nullable[vo.CampaignSortField] `json:"sortField"`
|
||||
SortOrder nullable.Nullable[vo.CampaignSendingOrder] `json:"sortOrder"`
|
||||
SendStartAt nullable.Nullable[time.Time] `json:"sendStartAt"`
|
||||
SendEndAt nullable.Nullable[time.Time] `json:"sendEndAt"`
|
||||
ConstraintWeekDays nullable.Nullable[vo.CampaignWeekDays] `json:"constraintWeekDays"`
|
||||
ConstraintStartTime nullable.Nullable[vo.CampaignTimeConstraint] `json:"constraintStartTime"`
|
||||
ConstraintEndTime nullable.Nullable[vo.CampaignTimeConstraint] `json:"constraintEndTime"`
|
||||
|
||||
Name nullable.Nullable[vo.String64] `json:"name"`
|
||||
|
||||
SaveSubmittedData nullable.Nullable[bool] `json:"saveSubmittedData"`
|
||||
IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"`
|
||||
IsTest nullable.Nullable[bool] `json:"isTest"`
|
||||
TemplateID nullable.Nullable[uuid.UUID] `json:"templateID"`
|
||||
Template *CampaignTemplate `json:"template"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
Company *Company `json:"company"`
|
||||
RecipientGroups []*RecipientGroup `json:"recipientGroups"`
|
||||
RecipientGroupIDs nullable.Nullable[[]*uuid.UUID] `json:"recipientGroupIDs,omitempty"`
|
||||
AllowDeny []*AllowDeny `json:"allowDeny"`
|
||||
AllowDenyIDs nullable.Nullable[[]*uuid.UUID] `json:"allowDenyIDs,omitempty"`
|
||||
DenyPageID nullable.Nullable[uuid.UUID] `json:"denyPageID,omitempty"`
|
||||
DenyPage *Page `json:"denyPage"`
|
||||
WebhookID nullable.Nullable[uuid.UUID] `json:"webhookID"`
|
||||
|
||||
// must not be set by a user
|
||||
NotableEventID nullable.Nullable[uuid.UUID] `json:"notableEventID"`
|
||||
NotableEventName string `json:"notableEventName"`
|
||||
}
|
||||
|
||||
// Validate checks if the campaign has a valid state
|
||||
func (c *Campaign) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", c.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("sortField", c.SortField); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("sortOrder", c.SortOrder); err != nil {
|
||||
return err
|
||||
}
|
||||
// if a start or end is set, then end must be equal or after the start
|
||||
if c.SendStartAt.IsSpecified() && !c.SendStartAt.IsNull() || (c.SendEndAt.IsSpecified() && !c.SendEndAt.IsNull()) {
|
||||
if err := validate.NullableFieldRequired("sendStartAt", c.SendStartAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("sendEndAt", c.SendEndAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.SendEndAt.MustGet().Before(c.SendStartAt.MustGet()) {
|
||||
return validate.WrapErrorWithField(errors.New("send end time must be after start time"), "sendEndAt")
|
||||
}
|
||||
}
|
||||
if err := validate.NullableFieldRequired("templateID", c.TemplateID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("RecipientGroupIDs", c.RecipientGroupIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.RecipientGroupIDs.MustGet()) == 0 {
|
||||
return validate.WrapErrorWithField(errors.New("must have at least one recipient group"), "RecipientGroupIDs")
|
||||
}
|
||||
if err := c.ValidateDenyPage(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if ConstraintWeekDays or ConstraintStartTime or ConstraintEndTime is set, then this is a 'scheduled type'
|
||||
// this requires that all fields are set and that end time is equal or after start time
|
||||
if (c.ConstraintWeekDays.IsSpecified() && !c.ConstraintWeekDays.IsNull()) ||
|
||||
(c.ConstraintStartTime.IsSpecified() && !c.ConstraintStartTime.IsNull()) ||
|
||||
(c.ConstraintEndTime.IsSpecified() && !c.ConstraintEndTime.IsNull()) {
|
||||
// check required fields are set
|
||||
if err := c.ValidateSendTimesSet(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintWeekDays", c.ConstraintWeekDays); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.ConstraintWeekDays.MustGet().Count() == 0 {
|
||||
return validate.WrapErrorWithField(errors.New("must have at least one day selected"), "ConstraintWeekDays")
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintStartTime", c.ConstraintStartTime); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintEndTime", c.ConstraintEndTime); err != nil {
|
||||
return err
|
||||
}
|
||||
// check that times and days are valid
|
||||
constraintStartTime := c.ConstraintStartTime.MustGet()
|
||||
constraintEndTime := c.ConstraintEndTime.MustGet()
|
||||
if constraintStartTime.IsAfter(constraintEndTime) {
|
||||
return validate.WrapErrorWithField(errors.New("constraint end time must be after start time"), "ConstraintEndTime")
|
||||
}
|
||||
if constraintStartTime.IsEqual(constraintEndTime) {
|
||||
return validate.WrapErrorWithField(errors.New("constraint end time must be after start time"), "ConstraintEndTime")
|
||||
}
|
||||
startAt := c.SendStartAt.MustGet()
|
||||
endAt := c.SendEndAt.MustGet()
|
||||
// check that selected weekdays are within the start and end date
|
||||
isWithin := c.ConstraintWeekDays.MustGet().IsWithin(&startAt, &endAt)
|
||||
if !isWithin {
|
||||
return validate.WrapErrorWithField(
|
||||
fmt.Errorf(
|
||||
"constraint week days must be within the start (%s) and end date (%s)",
|
||||
startAt.Format("2006-01-02"),
|
||||
endAt.Format("2006-01-02"),
|
||||
),
|
||||
"ConstraintWeekDays",
|
||||
)
|
||||
}
|
||||
}
|
||||
// ensure closeAt and anonymize is correctly set after other dates if set
|
||||
if c.CloseAt.IsSpecified() && !c.CloseAt.IsNull() {
|
||||
closeAt := c.CloseAt.MustGet()
|
||||
if v, err := c.SendEndAt.Get(); err == nil {
|
||||
if closeAt.Before(v) {
|
||||
return validate.WrapErrorWithField(errors.New("close at must be after end date"), "CloseAt")
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.AnonymizeAt.IsSpecified() && !c.AnonymizeAt.IsNull() {
|
||||
anonymizeAt := c.AnonymizeAt.MustGet()
|
||||
if v, err := c.CloseAt.Get(); err == nil {
|
||||
if anonymizeAt.Before(v) {
|
||||
return validate.WrapErrorWithField(errors.New("anonymize at must be after close date"), "AnonymizeAt")
|
||||
}
|
||||
}
|
||||
if v, err := c.SendEndAt.Get(); err != nil {
|
||||
if anonymizeAt.Before(v) {
|
||||
return validate.WrapErrorWithField(errors.New("anonymize at must be after end date"), "AnonymizeAt")
|
||||
}
|
||||
}
|
||||
if v, err := c.SendStartAt.Get(); err == nil {
|
||||
if anonymizeAt.Before(v) {
|
||||
return validate.WrapErrorWithField(errors.New("anonymize at must be after start date"), "AnonymizeAt")
|
||||
}
|
||||
}
|
||||
}
|
||||
// must not be set from api consumers
|
||||
if c.NotableEventID.IsSpecified() && !c.NotableEventID.IsNull() {
|
||||
c.NotableEventID.SetNull()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSendTimesSet checks that the send start and end times are set
|
||||
func (c *Campaign) ValidateSendTimesSet() error {
|
||||
if err := validate.NullableFieldRequired("sendStartAt", c.SendStartAt); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("sendEndAt", c.SendEndAt); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateScheduledType checks times related to a scheduled type campaign
|
||||
func (c *Campaign) ValidateScheduledTimes() error {
|
||||
if err := c.ValidateSendTimesSet(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintWeekDays", c.ConstraintWeekDays); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintStartTime", c.ConstraintStartTime); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ConstraintEndTime", c.ConstraintEndTime); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateNoSendTimesSet checks that the send start and end times are not set
|
||||
func (c *Campaign) ValidateNoSendTimesSet() error {
|
||||
if c.SendStartAt.IsSpecified() && !c.SendStartAt.IsNull() {
|
||||
return validate.WrapErrorWithField(errors.New("send start time must not be set"), "sendStartAt")
|
||||
}
|
||||
if c.SendEndAt.IsSpecified() && !c.SendEndAt.IsNull() {
|
||||
return validate.WrapErrorWithField(errors.New("send end time must not be set"), "sendEndAt")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateDenyPage checks that a deny page is set
|
||||
func (c *Campaign) ValidateDenyPage() error {
|
||||
if c.DenyPageID.IsSpecified() && !c.DenyPageID.IsNull() {
|
||||
if c.AllowDenyIDs.IsSpecified() && (c.AllowDenyIDs.IsNull() || len(c.AllowDenyIDs.MustGet()) == 0) {
|
||||
return validate.WrapErrorWithField(errors.New("requires a allow deny IDs to be set"), "denyPage")
|
||||
}
|
||||
}
|
||||
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 (c *Campaign) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if c.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if v, err := c.Name.Get(); err == nil {
|
||||
m["name"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.SortField.IsSpecified() {
|
||||
m["sort_field"] = nil
|
||||
if v, err := c.SortField.Get(); err == nil {
|
||||
m["sort_field"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.SortOrder.IsSpecified() {
|
||||
m["sort_order"] = nil
|
||||
if v, err := c.SortOrder.Get(); err == nil {
|
||||
m["sort_order"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.SendStartAt.IsSpecified() {
|
||||
m["send_start_at"] = nil
|
||||
if v, err := c.SendStartAt.Get(); err == nil {
|
||||
m["send_start_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.ConstraintWeekDays.IsSpecified() {
|
||||
m["constraint_week_days"] = nil
|
||||
if v, err := c.ConstraintWeekDays.Get(); err == nil {
|
||||
m["constraint_week_days"] = v.Int()
|
||||
}
|
||||
}
|
||||
if c.ConstraintStartTime.IsSpecified() {
|
||||
m["constraint_start_time"] = nil
|
||||
if v, err := c.ConstraintStartTime.Get(); err == nil {
|
||||
m["constraint_start_time"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.ConstraintEndTime.IsSpecified() {
|
||||
m["constraint_end_time"] = nil
|
||||
if v, err := c.ConstraintEndTime.Get(); err == nil {
|
||||
m["constraint_end_time"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.SendEndAt.IsSpecified() {
|
||||
m["send_end_at"] = nil
|
||||
if v, err := c.SendEndAt.Get(); err == nil {
|
||||
m["send_end_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.CloseAt.IsSpecified() {
|
||||
m["close_at"] = nil
|
||||
if v, err := c.CloseAt.Get(); err == nil {
|
||||
m["close_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.ClosedAt.IsSpecified() {
|
||||
m["closed_at"] = nil
|
||||
if v, err := c.ClosedAt.Get(); err == nil {
|
||||
m["closed_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.AnonymizeAt.IsSpecified() {
|
||||
m["anonymize_at"] = nil
|
||||
if v, err := c.AnonymizeAt.Get(); err == nil {
|
||||
m["anonymize_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.SaveSubmittedData.IsSpecified() {
|
||||
m["save_submitted_data"] = false
|
||||
if v, err := c.SaveSubmittedData.Get(); err == nil {
|
||||
m["save_submitted_data"] = v
|
||||
}
|
||||
}
|
||||
if c.IsTest.IsSpecified() {
|
||||
m["is_test"] = false
|
||||
if v, err := c.IsTest.Get(); err == nil {
|
||||
m["is_test"] = v
|
||||
}
|
||||
}
|
||||
if c.IsAnonymous.IsSpecified() {
|
||||
m["is_anonymous"] = false
|
||||
if v, err := c.IsAnonymous.Get(); err == nil {
|
||||
m["is_anonymous"] = v
|
||||
}
|
||||
}
|
||||
if c.TemplateID.IsSpecified() {
|
||||
m["campaign_template_id"] = nil
|
||||
if v, err := c.TemplateID.Get(); err == nil {
|
||||
m["campaign_template_id"] = v.String()
|
||||
}
|
||||
}
|
||||
if c.CompanyID.IsSpecified() {
|
||||
if c.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = c.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
allowDenyIsSet := c.AllowDenyIDs.IsSpecified() && !c.AllowDenyIDs.IsNull() && len(c.AllowDenyIDs.MustGet()) > 0
|
||||
if allowDenyIsSet {
|
||||
if v, err := c.DenyPageID.Get(); err == nil {
|
||||
m["deny_page_id"] = v.String()
|
||||
} else {
|
||||
m["deny_page_id"] = nil
|
||||
}
|
||||
} else {
|
||||
m["deny_page_id"] = nil
|
||||
}
|
||||
if c.WebhookID.IsSpecified() {
|
||||
m["webhook_id"] = nil
|
||||
if v, err := c.WebhookID.Get(); err == nil {
|
||||
m["webhook_id"] = v.String()
|
||||
}
|
||||
}
|
||||
if v, err := c.NotableEventID.Get(); err == nil {
|
||||
m["notable_event_id"] = v.String()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Close sets the close at timestamp to now
|
||||
// dont confuse with method Closed
|
||||
func (c *Campaign) Close() error {
|
||||
if c.ClosedAt.IsSpecified() && !c.ClosedAt.IsNull() {
|
||||
return errs.ErrCampaignAlreadyClosed
|
||||
}
|
||||
if c.CloseAt.IsSpecified() && !c.CloseAt.IsNull() {
|
||||
return errs.ErrCampaignAlreadySetToClose
|
||||
}
|
||||
c.CloseAt.Set(time.Now().UTC())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Closed sets the closed at timestamp to now
|
||||
// dont confuse with method Close
|
||||
func (c *Campaign) Closed() error {
|
||||
if c.ClosedAt.IsSpecified() && !c.ClosedAt.IsNull() {
|
||||
return errs.ErrCampaignAlreadyClosed
|
||||
}
|
||||
c.ClosedAt.Set(time.Now().UTC())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Anonymize sets the anonymized at timestamp
|
||||
func (c *Campaign) Anonymize() error {
|
||||
if c.AnonymizedAt.IsSpecified() && !c.AnonymizedAt.IsNull() {
|
||||
return errs.ErrCampaignAlreadyAnonymized
|
||||
}
|
||||
c.AnonymizedAt.Set(time.Now().UTC())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsActive returns true if the campaign is active
|
||||
func (c *Campaign) IsActive() bool {
|
||||
now := time.Now()
|
||||
if c.ClosedAt.IsSpecified() && !c.ClosedAt.IsNull() && c.ClosedAt.MustGet().Before(now) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSelfManaged returns true if the campaign is self managed
|
||||
func (c *Campaign) IsSelfManaged() bool {
|
||||
return c.SendStartAt.IsSpecified() && c.SendStartAt.IsNull() && c.SendEndAt.IsSpecified() && c.SendEndAt.IsNull()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
type CampaignEvent struct {
|
||||
ID *uuid.UUID `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
CampaignID *uuid.UUID `json:"campaignID"`
|
||||
IP *vo.OptionalString64 `json:"ip"`
|
||||
UserAgent *vo.OptionalString255 `json:"userAgent"`
|
||||
Data *vo.OptionalString1MB `json:"data"`
|
||||
AnonymizedID *uuid.UUID `json:"anonymizedID"`
|
||||
// if null the recipient has been anonymized
|
||||
RecipientID *uuid.UUID `json:"recipientID"`
|
||||
Recipient *Recipient `json:"recipient,omitempty"`
|
||||
EventID *uuid.UUID `json:"eventID"`
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/utils"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
)
|
||||
|
||||
// CampaignRecipient is a campaign recipient
|
||||
// this model must not be consumed from a endpoint
|
||||
type CampaignRecipient struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
CancelledAt nullable.Nullable[time.Time] `json:"cancelledAt"`
|
||||
SendAt nullable.Nullable[time.Time] `json:"sendAt"`
|
||||
LastAttemptAt nullable.Nullable[time.Time] `json:"lastAttemptAt"`
|
||||
SentAt nullable.Nullable[time.Time] `json:"sentAt"`
|
||||
SelfManaged nullable.Nullable[bool] `json:"selfManaged"`
|
||||
AnonymizedID nullable.Nullable[uuid.UUID] `json:"anonymizedID"`
|
||||
CampaignID nullable.Nullable[uuid.UUID] `json:"campaignID"`
|
||||
Campaign *Campaign `json:"campaign"`
|
||||
// null recipientID means that the data has been anonymized
|
||||
RecipientID nullable.Nullable[uuid.UUID] `json:"recipientID"`
|
||||
Recipient *Recipient `json:"recipient"`
|
||||
NotableEventID nullable.Nullable[uuid.UUID] `json:"notableEventID"`
|
||||
NotableEventName string `json:"notableEventName"`
|
||||
}
|
||||
|
||||
// Validate validates the campaign recipient
|
||||
func (c *CampaignRecipient) Validate() error {
|
||||
if err := validate.NullableFieldRequired("campaignID", c.CampaignID); err != nil {
|
||||
return err
|
||||
}
|
||||
anonymizedAtErr := validate.NullableFieldRequired("anonymizedID", c.AnonymizedID)
|
||||
recipientIDErr := validate.NullableFieldRequired("recipientID", c.RecipientID)
|
||||
if anonymizedAtErr == nil && recipientIDErr == nil {
|
||||
return recipientIDErr
|
||||
}
|
||||
if anonymizedAtErr != nil && recipientIDErr != nil {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("AnonymizedID can not be set with recipientID"),
|
||||
"recipientID",
|
||||
)
|
||||
}
|
||||
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 (c *CampaignRecipient) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if c.CancelledAt.IsSpecified() {
|
||||
m["cancelled_at"] = nil
|
||||
if v, err := c.CancelledAt.Get(); err == nil {
|
||||
m["cancelled_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.SendAt.IsSpecified() {
|
||||
m["send_at"] = nil
|
||||
if v, err := c.SendAt.Get(); err == nil {
|
||||
m["send_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.LastAttemptAt.IsSpecified() {
|
||||
m["last_attempt_at"] = nil
|
||||
if v, err := c.LastAttemptAt.Get(); err == nil {
|
||||
m["last_attempt_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.SentAt.IsSpecified() {
|
||||
m["sent_at"] = nil
|
||||
if v, err := c.SentAt.Get(); err == nil {
|
||||
m["sent_at"] = utils.RFC3339UTC(v)
|
||||
}
|
||||
}
|
||||
if c.SelfManaged.IsSpecified() {
|
||||
m["self_managed"] = nil
|
||||
if v, err := c.SelfManaged.Get(); err == nil {
|
||||
m["self_managed"] = v
|
||||
}
|
||||
}
|
||||
if c.CampaignID.IsSpecified() {
|
||||
m["campaign_id"] = nil
|
||||
if v, err := c.CampaignID.Get(); err == nil {
|
||||
m["campaign_id"] = v
|
||||
}
|
||||
}
|
||||
if c.RecipientID.IsSpecified() {
|
||||
m["recipient_id"] = nil
|
||||
if v, err := c.RecipientID.Get(); err == nil {
|
||||
m["recipient_id"] = v
|
||||
}
|
||||
}
|
||||
if c.NotableEventID.IsSpecified() {
|
||||
m["notable_event_id"] = nil
|
||||
if v, err := c.NotableEventID.Get(); err == nil {
|
||||
m["notable_event_id"] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type CampaignResultView struct {
|
||||
Recipients int64 `json:"recipients"`
|
||||
EmailsSent int64 `json:"emailsSent"`
|
||||
TrackingPixelLoaded int64 `json:"trackingPixelLoaded"`
|
||||
WebsiteLoaded int64 `json:"clickedLink"`
|
||||
SubmittedData int64 `json:"submittedData"`
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// CampaignTemplate is a campaign template
|
||||
type CampaignTemplate struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
|
||||
Name nullable.Nullable[vo.String64] `json:"name"`
|
||||
|
||||
DomainID nullable.Nullable[uuid.UUID] `json:"domainID"`
|
||||
Domain *Domain `json:"domain"`
|
||||
|
||||
BeforeLandingPageID nullable.Nullable[uuid.UUID] `json:"beforeLandingPageID"`
|
||||
BeforeLandingePage *Page `json:"beforeLandingPage"`
|
||||
|
||||
LandingPageID nullable.Nullable[uuid.UUID] `json:"landingPageID"`
|
||||
LandingPage *Page `json:"landingPage"`
|
||||
|
||||
AfterLandingPageID nullable.Nullable[uuid.UUID] `json:"afterLandingPageID"`
|
||||
AfterLandingPage *Page `json:"afterLandingPage"`
|
||||
|
||||
AfterLandingPageRedirectURL nullable.Nullable[vo.OptionalString255] `json:"afterLandingPageRedirectURL"`
|
||||
|
||||
URLIdentifierID nullable.Nullable[*uuid.UUID] `json:"urlIdentifierID"`
|
||||
URLIdentifier *Identifier `json:"urlIdentifier"`
|
||||
|
||||
StateIdentifierID nullable.Nullable[*uuid.UUID] `json:"stateIdentifierID"`
|
||||
StateIdentifier *Identifier `json:"stateIdentifier"`
|
||||
|
||||
URLPath nullable.Nullable[vo.URLPath] `json:"urlPath"`
|
||||
|
||||
EmailID nullable.Nullable[uuid.UUID] `json:"emailID"`
|
||||
Email *Email `json:"email"`
|
||||
|
||||
SMTPConfigurationID nullable.Nullable[uuid.UUID] `json:"smtpConfigurationID"`
|
||||
SMTPConfiguration *SMTPConfiguration `json:"smtpConfiguration"`
|
||||
|
||||
APISenderID nullable.Nullable[uuid.UUID] `json:"apiSenderID"`
|
||||
APISender *APISender `json:"apiSender"`
|
||||
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
Company *Company `json:"company"`
|
||||
|
||||
IsUsable nullable.Nullable[bool] `json:"isUsable"`
|
||||
}
|
||||
|
||||
// Validate checks if the campaign template has a valid state
|
||||
func (c *CampaignTemplate) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", c.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("urlIdentifierID", c.URLIdentifierID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("stateIdentifierID", c.StateIdentifierID); err != nil {
|
||||
return err
|
||||
}
|
||||
if a, err := c.URLIdentifierID.Get(); err == nil {
|
||||
if b, err := c.StateIdentifierID.Get(); err == nil {
|
||||
if a.String() == b.String() {
|
||||
return errs.NewValidationError(
|
||||
errors.New("URL and state identifier can not be the same"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := validate.NullableFieldRequired("urlPath", c.URLPath); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (c *CampaignTemplate) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if c.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := c.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if c.DomainID.IsSpecified() {
|
||||
if c.DomainID.IsNull() {
|
||||
m["domain_id"] = nil
|
||||
} else {
|
||||
m["domain_id"] = c.DomainID.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
if c.BeforeLandingPageID.IsSpecified() {
|
||||
if c.BeforeLandingPageID.IsNull() {
|
||||
m["before_landing_page_id"] = nil
|
||||
} else {
|
||||
m["before_landing_page_id"] = c.BeforeLandingPageID.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
if c.LandingPageID.IsSpecified() {
|
||||
if c.LandingPageID.IsNull() {
|
||||
m["landing_page_id"] = nil
|
||||
} else {
|
||||
m["landing_page_id"] = c.LandingPageID.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
if c.AfterLandingPageID.IsSpecified() {
|
||||
if c.AfterLandingPageID.IsNull() {
|
||||
m["after_landing_page_id"] = nil
|
||||
} else {
|
||||
m["after_landing_page_id"] = c.AfterLandingPageID.MustGet()
|
||||
}
|
||||
}
|
||||
if c.AfterLandingPageRedirectURL.IsSpecified() {
|
||||
if c.AfterLandingPageRedirectURL.IsNull() {
|
||||
m["after_landing_page_redirect_url"] = nil
|
||||
} else {
|
||||
m["after_landing_page_redirect_url"] = c.AfterLandingPageRedirectURL.MustGet().String()
|
||||
}
|
||||
}
|
||||
|
||||
if c.EmailID.IsSpecified() {
|
||||
if c.EmailID.IsNull() {
|
||||
m["email_id"] = nil
|
||||
} else {
|
||||
m["email_id"] = c.EmailID.MustGet()
|
||||
}
|
||||
}
|
||||
if c.SMTPConfigurationID.IsSpecified() {
|
||||
if c.SMTPConfigurationID.IsNull() {
|
||||
m["smtp_configuration_id"] = nil
|
||||
} else {
|
||||
m["smtp_configuration_id"] = c.SMTPConfigurationID.MustGet()
|
||||
}
|
||||
}
|
||||
if c.APISenderID.IsSpecified() {
|
||||
if c.APISenderID.IsNull() {
|
||||
m["api_sender_id"] = nil
|
||||
} else {
|
||||
m["api_sender_id"] = c.APISenderID.MustGet()
|
||||
}
|
||||
}
|
||||
|
||||
if c.CompanyID.IsSpecified() {
|
||||
if c.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = c.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
if v, err := c.URLIdentifierID.Get(); err == nil {
|
||||
m["url_identifier_id"] = v
|
||||
}
|
||||
if v, err := c.StateIdentifierID.Get(); err == nil {
|
||||
m["state_identifier_id"] = v
|
||||
}
|
||||
if v, err := c.URLPath.Get(); err == nil {
|
||||
m["url_path"] = v.String()
|
||||
}
|
||||
|
||||
_, errDomain := c.DomainID.Get()
|
||||
_, errSMTP := c.SMTPConfigurationID.Get()
|
||||
_, errAPISender := c.APISenderID.Get()
|
||||
_, errEmail := c.EmailID.Get()
|
||||
_, errLandingPage := c.LandingPageID.Get()
|
||||
|
||||
m["is_usable"] = errDomain == nil &&
|
||||
errEmail == nil &&
|
||||
errLandingPage == nil &&
|
||||
(errSMTP == nil || errAPISender == nil)
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type CampaignsStatView struct {
|
||||
Active int64 `json:"active"`
|
||||
Upcoming int64 `json:"upcoming"`
|
||||
Finished int64 `json:"finished"`
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Company is a company
|
||||
type Company struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String64] `json:"name"`
|
||||
}
|
||||
|
||||
// Validate checks if the Company configuration with a valid state
|
||||
func (c *Company) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", c.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (c *Company) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if c.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := c.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
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"`
|
||||
HostWebsite nullable.Nullable[bool] `json:"hostWebsite"`
|
||||
ManagedTLS nullable.Nullable[bool] `json:"managedTLS"`
|
||||
OwnManagedTLS nullable.Nullable[bool] `json:"ownManagedTLS"`
|
||||
// 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"`
|
||||
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
|
||||
}
|
||||
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
|
||||
|
||||
// cant both have managed and own managed tls
|
||||
if managedTLS, err := d.ManagedTLS.Get(); err == nil && managedTLS && ownManagedTLSSet {
|
||||
return errs.NewValidationError(errors.New(
|
||||
"Domain TLS can not both be managed and own managed",
|
||||
))
|
||||
}
|
||||
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.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()
|
||||
}
|
||||
}
|
||||
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"`
|
||||
HostWebsite bool `json:"hostWebsite"`
|
||||
ManagedTLS bool `json:"managedTLS"`
|
||||
OwnManagedTLS bool `json:"ownManagedTLS"`
|
||||
RedirectURL string `json:"redirectURL"`
|
||||
CompanyID *uuid.UUID `json:"companyID"`
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Email is a e-mail
|
||||
type Email struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String64] `json:"name"`
|
||||
MailEnvelopeFrom nullable.Nullable[vo.MailEnvelopeFrom] `json:"mailEnvelopeFrom"` // Bounce / Return-Path
|
||||
MailHeaderFrom nullable.Nullable[vo.Email] `json:"mailHeaderFrom"`
|
||||
MailHeaderSubject nullable.Nullable[vo.OptionalString255] `json:"mailHeaderSubject"`
|
||||
Content nullable.Nullable[vo.OptionalString1MB] `json:"content"`
|
||||
AddTrackingPixel nullable.Nullable[bool] `json:"addTrackingPixel"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Attachments []*Attachment `json:"attachments"`
|
||||
Company *Company `json:"company"`
|
||||
}
|
||||
|
||||
// Validate checks if the mail has a valid state
|
||||
func (m *Email) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", m.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("mailEnvelopeFrom", m.MailEnvelopeFrom); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("mailHeaderSubject", m.MailHeaderSubject); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("addTrackingPixel", m.MailHeaderFrom); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("Content", m.Content); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (s *Email) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if s.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := s.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if s.MailEnvelopeFrom.IsSpecified() {
|
||||
m["mail_from"] = nil
|
||||
if envelopeFrom, err := s.MailEnvelopeFrom.Get(); err == nil {
|
||||
m["mail_from"] = envelopeFrom.String()
|
||||
}
|
||||
}
|
||||
if s.MailHeaderFrom.IsSpecified() {
|
||||
m["from"] = nil
|
||||
if headerFrom, err := s.MailHeaderFrom.Get(); err == nil {
|
||||
m["from"] = headerFrom.String()
|
||||
}
|
||||
}
|
||||
if s.MailHeaderSubject.IsSpecified() {
|
||||
m["subject"] = nil
|
||||
if headerSubject, err := s.MailHeaderSubject.Get(); err == nil {
|
||||
m["subject"] = headerSubject.String()
|
||||
}
|
||||
}
|
||||
if s.Content.IsSpecified() {
|
||||
m["content"] = nil
|
||||
if content, err := s.Content.Get(); err == nil {
|
||||
m["content"] = content.String()
|
||||
}
|
||||
}
|
||||
if s.AddTrackingPixel.IsSpecified() {
|
||||
m["add_tracking_pixel"] = nil
|
||||
if addTrackingPixel, err := s.AddTrackingPixel.Get(); err == nil {
|
||||
m["add_tracking_pixel"] = addTrackingPixel
|
||||
}
|
||||
}
|
||||
if s.CompanyID.IsSpecified() {
|
||||
if s.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = s.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func NewEmailExample() *Email {
|
||||
return &Email{
|
||||
Name: nullable.NewNullableWithValue(
|
||||
*vo.NewString64Must("ExampleEmail"),
|
||||
),
|
||||
MailEnvelopeFrom: nullable.NewNullableWithValue(
|
||||
*vo.NewMailEnvelopeFromMust("sender@example.test"),
|
||||
),
|
||||
MailHeaderFrom: nullable.NewNullableWithValue(
|
||||
*vo.NewEmailMust("Mallory <m@example.test>"),
|
||||
),
|
||||
MailHeaderSubject: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString255Must("SubjectLine"),
|
||||
),
|
||||
Content: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString1MBMust("Content"),
|
||||
),
|
||||
AddTrackingPixel: nullable.NewNullableWithValue(true),
|
||||
}
|
||||
}
|
||||
|
||||
// EmailOverview is a e-mail model without content and attachments
|
||||
type EmailOverview struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String64] `json:"name"`
|
||||
MailEnvelopeFrom nullable.Nullable[vo.MailEnvelopeFrom] `json:"mailEnvelopeFrom"` // Bounce / Return-Path
|
||||
MailHeaderFrom nullable.Nullable[vo.Email] `json:"mailHeaderFrom"`
|
||||
MailHeaderSubject nullable.Nullable[vo.OptionalString255] `json:"mailHeaderSubject"`
|
||||
AddTrackingPixel nullable.Nullable[bool] `json:"addTrackingPixel"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Company *Company `json:"company"`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
)
|
||||
|
||||
type Identifier struct {
|
||||
ID nullable.Nullable[*uuid.UUID] `json:"id"`
|
||||
Name nullable.Nullable[string] `json:"name"`
|
||||
}
|
||||
|
||||
func (i *Identifier) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", i.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Identifier) ToDBMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
if v, err := i.Name.Get(); err == nil {
|
||||
m["name"] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Option is an Option
|
||||
type Option struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
Key vo.String64 `json:"key"`
|
||||
Value vo.OptionalString1MB `json:"value"`
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
|
||||
Company *Company `json:"-"`
|
||||
}
|
||||
|
||||
// Validate checks if the page has a valid state
|
||||
func (p *Page) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", p.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("content", p.Content); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (p *Page) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if p.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := p.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if p.Content.IsSpecified() {
|
||||
m["content"] = nil
|
||||
if content, err := p.Content.Get(); err == nil {
|
||||
m["content"] = content.String()
|
||||
}
|
||||
}
|
||||
if p.CompanyID.IsSpecified() {
|
||||
if p.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = p.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Recipient is a Recipient
|
||||
type Recipient struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Email nullable.Nullable[vo.Email] `json:"email"`
|
||||
Phone nullable.Nullable[vo.OptionalString127] `json:"phone"`
|
||||
ExtraIdentifier nullable.Nullable[vo.OptionalString127] `json:"extraIdentifier"`
|
||||
FirstName nullable.Nullable[vo.OptionalString127] `json:"firstName"`
|
||||
LastName nullable.Nullable[vo.OptionalString127] `json:"lastName"`
|
||||
Position nullable.Nullable[vo.OptionalString127] `json:"position"`
|
||||
Department nullable.Nullable[vo.OptionalString127] `json:"department"`
|
||||
City nullable.Nullable[vo.OptionalString127] `json:"city"`
|
||||
Country nullable.Nullable[vo.OptionalString127] `json:"country"`
|
||||
Misc nullable.Nullable[vo.OptionalString127] `json:"misc"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Company *Company `json:"-"`
|
||||
Groups nullable.Nullable[[]*RecipientGroup] `json:"groups"`
|
||||
}
|
||||
|
||||
// Validate checks if the recipient has a valid state
|
||||
func (r *Recipient) Validate() error {
|
||||
if err := validate.NullableFieldRequired("email", r.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NullifyEmptyOptionals sets empty values to a nullable null, so they are not overwritten
|
||||
func (r *Recipient) NullifyEmptyOptionals() {
|
||||
if r.Phone.IsSpecified() && !r.Phone.IsNull() && r.Phone.MustGet().String() == "" {
|
||||
r.Phone.SetNull()
|
||||
}
|
||||
if r.ExtraIdentifier.IsSpecified() && !r.ExtraIdentifier.IsNull() && r.ExtraIdentifier.MustGet().String() == "" {
|
||||
r.ExtraIdentifier.SetNull()
|
||||
}
|
||||
if r.FirstName.IsSpecified() && !r.FirstName.IsNull() && r.FirstName.MustGet().String() == "" {
|
||||
r.FirstName.SetNull()
|
||||
}
|
||||
|
||||
if r.LastName.IsSpecified() && !r.LastName.IsNull() && r.LastName.MustGet().String() == "" {
|
||||
r.LastName.SetNull()
|
||||
}
|
||||
if r.Position.IsSpecified() && !r.Position.IsNull() && r.Position.MustGet().String() == "" {
|
||||
r.Position.SetNull()
|
||||
}
|
||||
if r.Department.IsSpecified() && !r.Department.IsNull() && r.Department.MustGet().String() == "" {
|
||||
r.Department.SetNull()
|
||||
}
|
||||
if r.City.IsSpecified() && !r.City.IsNull() && r.City.MustGet().String() == "" {
|
||||
r.City.SetNull()
|
||||
}
|
||||
if r.Country.IsSpecified() && !r.Country.IsNull() && r.Country.MustGet().String() == "" {
|
||||
r.Country.SetNull()
|
||||
}
|
||||
if r.Misc.IsSpecified() && !r.Misc.IsNull() && r.Misc.MustGet().String() == "" {
|
||||
r.Misc.SetNull()
|
||||
}
|
||||
}
|
||||
|
||||
// EmptyStringNulledOptionals sets nulled optional values to a empty string or zero value.
|
||||
func (r *Recipient) EmptyStringNulledOptionals() {
|
||||
if r.Phone.IsSpecified() && r.Phone.IsNull() {
|
||||
r.Phone.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.ExtraIdentifier.IsSpecified() && r.ExtraIdentifier.IsNull() {
|
||||
r.ExtraIdentifier.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.FirstName.IsSpecified() && r.FirstName.IsNull() {
|
||||
r.FirstName.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.LastName.IsSpecified() && r.LastName.IsNull() {
|
||||
r.LastName.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.Position.IsSpecified() && r.Position.IsNull() {
|
||||
r.Position.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.Department.IsSpecified() && r.Department.IsNull() {
|
||||
r.Department.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.City.IsSpecified() && r.City.IsNull() {
|
||||
r.City.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.Country.IsSpecified() && r.Country.IsNull() {
|
||||
r.Country.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
if r.Misc.IsSpecified() && r.Misc.IsNull() {
|
||||
r.Misc.Set(*vo.NewOptionalString127Must(""))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (r *Recipient) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if r.Email.IsSpecified() {
|
||||
m["email"] = nil
|
||||
if email, err := r.Email.Get(); err == nil {
|
||||
if email.String() == "" {
|
||||
m["email"] = nil // due to the unique constraint
|
||||
} else {
|
||||
m["email"] = email.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.Phone.IsSpecified() {
|
||||
m["phone"] = nil
|
||||
if phone, err := r.Phone.Get(); err == nil {
|
||||
if phone.String() == "" {
|
||||
m["phone"] = nil // due to the unique constraint
|
||||
} else {
|
||||
m["phone"] = phone.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.ExtraIdentifier.IsSpecified() {
|
||||
m["extra_identifier"] = nil
|
||||
if extraIdentifier, err := r.ExtraIdentifier.Get(); err == nil {
|
||||
if extraIdentifier.String() == "" {
|
||||
m["extra_identifier"] = nil // due to the unique constraint
|
||||
} else {
|
||||
m["extra_identifier"] = extraIdentifier.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.FirstName.IsSpecified() {
|
||||
m["first_name"] = nil
|
||||
if firstName, err := r.FirstName.Get(); err == nil {
|
||||
m["first_name"] = firstName.String()
|
||||
}
|
||||
}
|
||||
if r.LastName.IsSpecified() {
|
||||
m["last_name"] = nil
|
||||
if lastName, err := r.LastName.Get(); err == nil {
|
||||
m["last_name"] = lastName.String()
|
||||
}
|
||||
}
|
||||
if r.Position.IsSpecified() {
|
||||
m["position"] = nil
|
||||
if position, err := r.Position.Get(); err == nil {
|
||||
m["position"] = position.String()
|
||||
}
|
||||
}
|
||||
if r.Department.IsSpecified() {
|
||||
m["department"] = nil
|
||||
if department, err := r.Department.Get(); err == nil {
|
||||
m["department"] = department.String()
|
||||
}
|
||||
}
|
||||
if r.City.IsSpecified() {
|
||||
m["city"] = nil
|
||||
if city, err := r.City.Get(); err == nil {
|
||||
m["city"] = city.String()
|
||||
}
|
||||
}
|
||||
if r.Country.IsSpecified() {
|
||||
m["country"] = nil
|
||||
if country, err := r.Country.Get(); err == nil {
|
||||
m["country"] = country.String()
|
||||
}
|
||||
}
|
||||
if r.Misc.IsSpecified() {
|
||||
m["misc"] = nil
|
||||
if misc, err := r.Misc.Get(); err == nil {
|
||||
m["misc"] = misc.String()
|
||||
}
|
||||
}
|
||||
if r.CompanyID.IsSpecified() {
|
||||
if r.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = r.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func NewRecipientExample() *Recipient {
|
||||
return &Recipient{
|
||||
Email: nullable.NewNullableWithValue(
|
||||
*vo.NewEmailMust("Rick <rick@company.test>"),
|
||||
),
|
||||
Phone: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("1234567890"),
|
||||
),
|
||||
ExtraIdentifier: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("ExtraIdentifier"),
|
||||
),
|
||||
FirstName: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("Rick"),
|
||||
),
|
||||
LastName: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("Xanders"),
|
||||
),
|
||||
Position: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("CEO"),
|
||||
),
|
||||
Department: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("IT"),
|
||||
),
|
||||
City: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("Fredericia"),
|
||||
),
|
||||
Country: nullable.NewNullableWithValue(
|
||||
*vo.NewOptionalString127Must("Denmark"),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type RecipientCampaignEvent struct {
|
||||
CampaignEvent
|
||||
|
||||
// event name
|
||||
Name string `json:"name"`
|
||||
CampaignName string `json:"campaignName"`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
type RecipientCampaignStatsView struct {
|
||||
CampaignsParticiated int64 `json:"campaignsParticiated"`
|
||||
CampaignsTrackingPixelLoaded int64 `json:"campaignsTrackingPixelLoaded"`
|
||||
CampaignsPhishingPageLoaded int64 `json:"campaignsPhishingPageLoaded"`
|
||||
CampaignsDataSubmitted int64 `json:"campaignsDataSubmitted"`
|
||||
RepeatLinkClicks int64 `json:"repeatLinkClicks"`
|
||||
RepeatSubmissions int64 `json:"repeatSubmissions"`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
const RECIPIENT_COUNT_NOT_LOADED = int64(-1)
|
||||
const RECIPIENT_GROUP_COUNT_NOT_LOADED = int64(-1)
|
||||
|
||||
// RecipientGroup is an entity for recipient group
|
||||
type RecipientGroup struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String127] `json:"name"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
|
||||
Recipients []*Recipient `json:"-"`
|
||||
IsRecipientsLoaded bool `json:"-"`
|
||||
RecipientCount nullable.Nullable[int64] `json:"recipientCount"`
|
||||
IsRecipientCountLoaded bool `json:"-"`
|
||||
Company *Company `json:"-"`
|
||||
}
|
||||
|
||||
// Validate checks if the recipient group has a valid state
|
||||
func (rg *RecipientGroup) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", rg.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (rg *RecipientGroup) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if rg.Name.IsSpecified() {
|
||||
name := rg.Name.MustGet()
|
||||
m["name"] = name.String()
|
||||
}
|
||||
if rg.CompanyID.IsSpecified() {
|
||||
if rg.CompanyID.IsNull() {
|
||||
m["company_id"] = nil
|
||||
} else {
|
||||
m["company_id"] = rg.CompanyID.MustGet()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type RecipientGroupRecipient struct {
|
||||
ID *uuid.UUID
|
||||
RecipientID *uuid.UUID
|
||||
RecipientGroupID *uuid.UUID
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
// RecipientView extends Recipient with additional presentation fields
|
||||
type RecipientView struct {
|
||||
*Recipient // Embed the base Recipient model
|
||||
IsRepeatOffender bool `json:"isRepeatOffender"`
|
||||
}
|
||||
|
||||
// NewRecipientView creates a RecipientView from a Recipient
|
||||
func NewRecipientView(r *Recipient) *RecipientView {
|
||||
return &RecipientView{
|
||||
Recipient: r,
|
||||
IsRepeatOffender: false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type Result[T any] struct {
|
||||
Rows []*T `json:"rows"`
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
}
|
||||
|
||||
func NewResult[T any](rows []*T) *Result[T] {
|
||||
return &Result[T]{
|
||||
Rows: rows,
|
||||
HasNextPage: false,
|
||||
}
|
||||
}
|
||||
|
||||
func NewEmptyResult[T any]() *Result[T] {
|
||||
t := []*T{}
|
||||
return &Result[T]{
|
||||
Rows: t,
|
||||
HasNextPage: false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
)
|
||||
|
||||
// Role is user role and defines the permissions of the user
|
||||
type Role struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Validate checks if the role has a valid state
|
||||
func (r *Role) Validate() error {
|
||||
if err := validateRoleName(r.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (r *Role) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if r.Name != "" {
|
||||
m["name"] = r.Name
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *Role) IsAuthorized(permission string) bool {
|
||||
perms := r.Permissions()
|
||||
for _, perm := range perms {
|
||||
if perm == permission {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Permissions gets the permissions of the role
|
||||
func (r *Role) Permissions() []string {
|
||||
perms, ok := data.RolePermissions[r.Name]
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
return perms
|
||||
}
|
||||
|
||||
// IsSuperAdministrator checks if the role is a super administrator
|
||||
func (r *Role) IsSuperAdministrator() bool {
|
||||
return r.Name == data.RoleSuperAdministrator
|
||||
}
|
||||
|
||||
func validateRoleName(name string) error {
|
||||
// ensure only valid role names are used
|
||||
switch name {
|
||||
case data.RoleSystem:
|
||||
case data.RoleSuperAdministrator:
|
||||
case data.RoleCompanyUser:
|
||||
default:
|
||||
return fmt.Errorf("invalid role name: %s", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
)
|
||||
|
||||
// todo move this to a global place like data or a config of sorts
|
||||
const (
|
||||
SessionIdleTimeout = 24 * time.Hour
|
||||
SessionMaxAgeAt = 24 * 3 * time.Hour
|
||||
)
|
||||
|
||||
// used in runtime for API requests
|
||||
// api session is only for the single request, more like a api session contex
|
||||
type APISession struct {
|
||||
IP string
|
||||
UserID *uuid.UUID
|
||||
}
|
||||
|
||||
// Session reprensents a user session
|
||||
// no Validate or ToDBMap as it is never created from user input
|
||||
type Session struct {
|
||||
ID *uuid.UUID `json:"id,omitempty"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
ExpiresAt *time.Time `json:"expiresAt"`
|
||||
MaxAgeAt *time.Time `json:"maxAgeAt"`
|
||||
IP string `json:"ip"`
|
||||
User *User `json:"user"`
|
||||
IsUserLoaded bool `json:"-"`
|
||||
IsAPITokenRequest bool
|
||||
}
|
||||
|
||||
// NewSystemSession creates a new system session
|
||||
func NewSystemSession() (*Session, error) {
|
||||
id, err := uuid.Parse(data.SystemSessionID)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
now := time.Now()
|
||||
longTimeFromNow := now.Add(time.Duration(420 * time.Now().Year())).UTC()
|
||||
expiresAt := &longTimeFromNow
|
||||
maxAgeAt := &longTimeFromNow
|
||||
user, err := NewSystemUser()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &Session{
|
||||
ID: &id,
|
||||
ExpiresAt: expiresAt,
|
||||
MaxAgeAt: maxAgeAt,
|
||||
IP: "127.0.0.1",
|
||||
User: user,
|
||||
IsUserLoaded: true,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// Renew updates the session
|
||||
func (s *Session) Renew(lease time.Duration) {
|
||||
now := time.Now().UTC()
|
||||
expiresAt := now.Add(lease)
|
||||
s.ExpiresAt = &expiresAt
|
||||
}
|
||||
|
||||
// IsExpired returns true if the session is expired
|
||||
func (s *Session) IsExpired() bool {
|
||||
// is total lifetime over max lifetime?
|
||||
if time.Now().After(*s.MaxAgeAt) {
|
||||
return true
|
||||
}
|
||||
// is idle timeout over?
|
||||
if time.Now().After(*s.ExpiresAt) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// SMTPConfiguration is a configuration for sending mails
|
||||
type SMTPConfiguration struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.String127] `json:"name"`
|
||||
Host nullable.Nullable[vo.String255] `json:"host"`
|
||||
Port nullable.Nullable[vo.Port] `json:"port"`
|
||||
Username nullable.Nullable[vo.OptionalString255] `json:"username"`
|
||||
Password nullable.Nullable[vo.OptionalString255] `json:"password"`
|
||||
IgnoreCertErrors nullable.Nullable[bool] `json:"ignoreCertErrors"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
Company *Company `json:"company"`
|
||||
Headers []*SMTPHeader `json:"headers"`
|
||||
}
|
||||
|
||||
// Validate checks if the SMTP configuration has a valid state
|
||||
func (s *SMTPConfiguration) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", s.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("host", s.Host); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("port", s.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("username", s.Username); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("password", s.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("ignoreCertErrors", s.IgnoreCertErrors); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (s *SMTPConfiguration) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if s.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := s.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if s.Host.IsSpecified() {
|
||||
m["host"] = nil
|
||||
if host, err := s.Host.Get(); err == nil {
|
||||
m["host"] = host.String()
|
||||
}
|
||||
}
|
||||
if s.Port.IsSpecified() {
|
||||
m["port"] = nil
|
||||
if port, err := s.Port.Get(); err == nil {
|
||||
m["port"] = port.Uint16()
|
||||
}
|
||||
}
|
||||
if s.Username.IsSpecified() {
|
||||
m["username"] = nil
|
||||
if username, err := s.Username.Get(); err == nil {
|
||||
m["username"] = username.String()
|
||||
}
|
||||
}
|
||||
if s.Password.IsSpecified() {
|
||||
m["password"] = nil
|
||||
if password, err := s.Password.Get(); err == nil {
|
||||
m["password"] = password.String()
|
||||
}
|
||||
}
|
||||
if s.IgnoreCertErrors.IsSpecified() {
|
||||
m["ignore_cert_errors"] = nil
|
||||
if ignoreCertErrors, err := s.IgnoreCertErrors.Get(); err == nil {
|
||||
m["ignore_cert_errors"] = ignoreCertErrors
|
||||
}
|
||||
}
|
||||
if v, err := s.CompanyID.Get(); err == nil {
|
||||
m["company_id"] = v.String()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// SMTPHeader is a header for a specific SMTP configuration
|
||||
type SMTPHeader struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
SmtpID nullable.Nullable[uuid.UUID] `json:"smtpID"`
|
||||
Key nullable.Nullable[vo.String127] `json:"key"`
|
||||
Value nullable.Nullable[vo.String255] `json:"value"`
|
||||
}
|
||||
|
||||
func (s *SMTPHeader) Validate() error {
|
||||
if err := validate.NullableFieldRequired("smtpID", s.SmtpID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("key", s.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("value", s.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SMTPHeader) ToDBMap() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
if s.SmtpID.IsSpecified() {
|
||||
if smtpID, err := s.SmtpID.Get(); err == nil {
|
||||
m["smtp_configuration_id"] = smtpID.String()
|
||||
}
|
||||
}
|
||||
if s.Key.IsSpecified() {
|
||||
m["key"] = nil
|
||||
if key, err := s.Key.Get(); err == nil {
|
||||
m["key"] = key.String()
|
||||
}
|
||||
}
|
||||
if s.Value.IsSpecified() {
|
||||
m["value"] = nil
|
||||
if value, err := s.Value.Get(); err == nil {
|
||||
m["value"] = value.String()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
type SSOOption struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ClientID vo.OptionalString64 `json:"clientID"`
|
||||
TenantID vo.OptionalString64 `json:"tenantID"`
|
||||
ClientSecret vo.OptionalString1024 `json:"clientSecret"`
|
||||
RedirectURL vo.OptionalString1024 `json:"redirectURL"`
|
||||
}
|
||||
|
||||
func NewSSOOptionDefault() *SSOOption {
|
||||
return &SSOOption{
|
||||
Enabled: false,
|
||||
ClientID: *vo.NewEmptyOptionalString64(),
|
||||
TenantID: *vo.NewEmptyOptionalString64(),
|
||||
ClientSecret: *vo.NewEmptyOptionalString1024(),
|
||||
RedirectURL: *vo.NewEmptyOptionalString1024(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSSOOptionFromJSON(jsonData []byte) (*SSOOption, error) {
|
||||
option := &SSOOption{}
|
||||
err := json.Unmarshal(jsonData, option)
|
||||
if err != nil {
|
||||
return nil, validate.WrapErrorWithField(
|
||||
errs.NewValidationError(
|
||||
errors.New("invalid format"),
|
||||
),
|
||||
"Option",
|
||||
)
|
||||
}
|
||||
return option, nil
|
||||
}
|
||||
|
||||
func NewSSOOptionFromOption(option *Option) (*SSOOption, error) {
|
||||
if option == nil {
|
||||
return nil, fmt.Errorf("option cannot be nil")
|
||||
}
|
||||
ssooption, err := NewSSOOptionFromJSON([]byte(option.Value.String()))
|
||||
if err != nil {
|
||||
return nil, validate.WrapErrorWithField(
|
||||
errs.NewValidationError(
|
||||
errors.New("invalid format"),
|
||||
),
|
||||
"SSOOption",
|
||||
)
|
||||
}
|
||||
return ssooption, nil
|
||||
}
|
||||
|
||||
func (l *SSOOption) ToJSON() ([]byte, error) {
|
||||
return json.Marshal(l)
|
||||
}
|
||||
|
||||
func (l *SSOOption) ToOption() (*Option, error) {
|
||||
json, err := l.ToJSON()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
str, err := vo.NewOptionalString1MB(string(json))
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &Option{
|
||||
Key: *vo.NewString64Must(data.OptionKeyAdminSSOLogin),
|
||||
Value: *str,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/data"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
const SYSTEM_USER_ID = "3eb19071-fbbb-4736-9991-02ba532a7849"
|
||||
|
||||
// User is a user of the system, including the company and role
|
||||
type User struct {
|
||||
ID nullable.Nullable[uuid.UUID] `json:"id"`
|
||||
CreatedAt *time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
Name nullable.Nullable[vo.UserFullname] `json:"name"`
|
||||
Username nullable.Nullable[vo.Username] `json:"username"`
|
||||
Email nullable.Nullable[vo.Email] `json:"email"`
|
||||
RequirePasswordRenew nullable.Nullable[bool] `json:"requirePasswordRenew"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
Company *Company `json:"company"`
|
||||
RoleID nullable.Nullable[uuid.UUID] `json:"roleID"`
|
||||
Role *Role `json:"role"`
|
||||
SSOID nullable.Nullable[string] `json:"ssoID"`
|
||||
// apiKey is only get/set externally from this and never output except when created
|
||||
}
|
||||
|
||||
// Validate checks if the user has a valid state
|
||||
func (u *User) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", u.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("username", u.Username); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("email", u.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (u *User) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if u.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := u.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if u.Username.IsSpecified() {
|
||||
m["username"] = nil
|
||||
if username, err := u.Username.Get(); err == nil {
|
||||
m["username"] = username.String()
|
||||
}
|
||||
}
|
||||
if u.Email.IsSpecified() {
|
||||
m["email"] = nil
|
||||
if email, err := u.Email.Get(); err == nil {
|
||||
m["email"] = email.String()
|
||||
}
|
||||
}
|
||||
if u.RequirePasswordRenew.IsSpecified() {
|
||||
m["require_password_renew"] = nil
|
||||
if requirePasswordRenew, err := u.RequirePasswordRenew.Get(); err == nil {
|
||||
m["require_password_renew"] = requirePasswordRenew
|
||||
}
|
||||
}
|
||||
if u.CompanyID.IsSpecified() {
|
||||
m["company_id"] = nil
|
||||
if companyID, err := u.CompanyID.Get(); err == nil {
|
||||
m["company_id"] = companyID
|
||||
}
|
||||
}
|
||||
if u.RoleID.IsSpecified() {
|
||||
m["role_id"] = nil
|
||||
if roleID, err := u.RoleID.Get(); err == nil {
|
||||
m["role_id"] = roleID
|
||||
}
|
||||
}
|
||||
|
||||
if u.SSOID.IsSpecified() {
|
||||
m["sso_id"] = nil
|
||||
if ssoID, err := u.SSOID.Get(); err == nil {
|
||||
m["sso_id"] = ssoID
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// UserUpsertRequest is a request for creating a new user
|
||||
type UserUpsertRequest struct {
|
||||
Username vo.Username `json:"username"`
|
||||
Password vo.ReasonableLengthPassword `json:"password"`
|
||||
Email vo.Email `json:"email"`
|
||||
Fullname vo.UserFullname `json:"fullname"`
|
||||
}
|
||||
|
||||
// UserChangeEmailRequest is a request for changing the email of a user
|
||||
type UserChangeEmailRequest struct {
|
||||
Email vo.Email `json:"email"`
|
||||
}
|
||||
|
||||
// UserChangeFullnameRequest is the change fullname request
|
||||
type UserChangeFullnameRequest struct {
|
||||
NewFullname vo.UserFullname `json:"fullname"`
|
||||
}
|
||||
|
||||
type InvalidateAllSessionRequest struct {
|
||||
UserID *uuid.UUID `json:"userID"`
|
||||
}
|
||||
|
||||
// UserChangePasswordRequest is a request for changing password
|
||||
type UserChangePasswordRequest struct {
|
||||
CurrentPassword vo.ReasonableLengthPassword `json:"currentPassword" binding:"required"`
|
||||
NewPassword vo.ReasonableLengthPassword `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// UserChangeUsernameOnLoggedInRequest is the change username request
|
||||
type UserChangeUsernameOnLoggedInRequest struct {
|
||||
NewUsername vo.Username `json:"username"`
|
||||
}
|
||||
|
||||
// NewUser creates a new user which is used for internal system actions and
|
||||
// cant not be used to login or by a human.
|
||||
func NewSystemUser() (*User, error) {
|
||||
role := &Role{
|
||||
Name: data.RoleSystem,
|
||||
}
|
||||
id := uuid.MustParse(SYSTEM_USER_ID)
|
||||
return &User{
|
||||
ID: nullable.NewNullableWithValue(id),
|
||||
Name: nullable.NewNullableWithValue(*vo.NewUserFullnameMust("system")),
|
||||
Username: nullable.NewNullableWithValue(*vo.NewUsernameMust("system")),
|
||||
Email: nullable.NewNullableWithValue(
|
||||
*vo.NewEmailMust("system@example.com"),
|
||||
),
|
||||
RequirePasswordRenew: nullable.NewNullableWithValue(false),
|
||||
Company: nil,
|
||||
Role: role,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type APIUser struct {
|
||||
APIKeyHash [32]byte
|
||||
ID *uuid.UUID
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/validate"
|
||||
"github.com/phishingclub/phishingclub/vo"
|
||||
)
|
||||
|
||||
// Webhook is a gorm data model for webhooks
|
||||
type Webhook 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.String127] `json:"name"`
|
||||
URL nullable.Nullable[vo.String1024] `json:"url"`
|
||||
Secret nullable.Nullable[vo.OptionalString1024] `json:"secret"`
|
||||
}
|
||||
|
||||
// Validate runs the validations for this struct
|
||||
func (w *Webhook) Validate() error {
|
||||
if err := validate.NullableFieldRequired("name", w.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.NullableFieldRequired("secret", w.URL); err != nil {
|
||||
return err
|
||||
}
|
||||
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 (w *Webhook) ToDBMap() map[string]any {
|
||||
m := map[string]any{}
|
||||
if w.Name.IsSpecified() {
|
||||
m["name"] = nil
|
||||
if name, err := w.Name.Get(); err == nil {
|
||||
m["name"] = name.String()
|
||||
}
|
||||
}
|
||||
if w.URL.IsSpecified() {
|
||||
m["url"] = nil
|
||||
if url, err := w.URL.Get(); err == nil {
|
||||
m["url"] = url.String()
|
||||
}
|
||||
}
|
||||
if w.Secret.IsSpecified() {
|
||||
m["secret"] = nil
|
||||
if secret, err := w.Secret.Get(); err == nil {
|
||||
m["secret"] = secret.String()
|
||||
}
|
||||
}
|
||||
if v, err := w.CompanyID.Get(); err == nil {
|
||||
m["company_id"] = v.String()
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in new issue
Block a user