Skip to content

Commit

Permalink
one more pass
Browse files Browse the repository at this point in the history
Signed-off-by: Sarah Funkhouser <[email protected]>
  • Loading branch information
golanglemonade committed Sep 5, 2024
1 parent 0877c7c commit bc8679a
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 95 deletions.
45 changes: 45 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,48 @@ steps:
command: ["go", "test", "-coverprofile=coverage.out", "./..."]
environment:
- "GOTOOLCHAIN=auto"
artifact_paths: ["coverage.out"]
- group: ":closed_lock_with_key: Security Checks"
depends_on: "go_test"
key: "security"
steps:
- label: ":closed_lock_with_key: gosec"
key: "gosec"
plugins:
- docker#v5.11.0:
image: "registry.hub.docker.com/securego/gosec:2.20.0"
command: ["-no-fail", "-exclude-generated", "-fmt sonarqube", "-out", "results.txt", "./..."]
environment:
- "GOTOOLCHAIN=auto"
artifact_paths: ["results.txt"]
- label: ":github: upload PR reports"
key: "scan-upload-pr"
if: build.pull_request.id != null
depends_on: ["gosec", "go_test"]
plugins:
- artifacts#v1.9.4:
download: "results.txt"
- artifacts#v1.9.4:
download: "coverage.out"
step: "go_test"
- docker#v5.11.0:
image: "sonarsource/sonar-scanner-cli:5"
environment:
- "SONAR_TOKEN"
- "SONAR_HOST_URL=$SONAR_HOST"
- "SONAR_SCANNER_OPTS=-Dsonar.pullrequest.branch=$BUILDKITE_BRANCH -Dsonar.pullrequest.base=$BUILDKITE_PULL_REQUEST_BASE_BRANCH -Dsonar.pullrequest.key=$BUILDKITE_PULL_REQUEST"
- label: ":github: upload reports"
key: "scan-upload"
if: build.branch == "main"
depends_on: ["gosec", "go_test"]
plugins:
- artifacts#v1.9.4:
download: results.txt
- artifacts#v1.9.4:
download: coverage.out
step: "go_test"
- docker#v5.11.0:
image: "sonarsource/sonar-scanner-cli:5"
environment:
- "SONAR_TOKEN"
- "SONAR_HOST_URL=$SONAR_HOST"
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,21 @@ import (
)

func main() {
sender, err := resend.New("your_resend_api_token")
if err != nil {
log.Fatal(err)
}

msg := newman.NewEmailMessage("[email protected]", []string{"[email protected]"}, "Isnt sending emails with golang fun?", "Oh Yes! Mark my words, Seinfeld! Your day of reckoning is coming")

if err := sender.SendEmail(msg); err != nil {
log.Fatal(err)
}
sender, err := resend.New("your_resend_api_token")
if err != nil {
log.Fatal(err)
}

msg := newman.NewEmailMessageWithOptions(
newman.WithFrom("[email protected]"),
newman.WithTo([]string{"[email protected]"}),
newman.WithSubject("Isn't sending emails with golang fun?"),
newman.WithHTML("<p>Oh Yes! Mark my words, Seinfeld! Your day of reckoning is coming</p>"),
)

if err := sender.SendEmail(msg); err != nil {
log.Fatal(err)
}
}
```

Expand Down
19 changes: 14 additions & 5 deletions newman.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,23 @@ type EmailMessage = shared.EmailMessage
// Attachment represents an email attachment with its filename and content
type Attachment = shared.Attachment

// Tag is used to define custom metadata for message
type Tag = shared.Tag

// NewEmailMessage creates a new EmailMessage with the required fields
func NewEmailMessage(from string, to []string, subject string, body string) *EmailMessage {
return shared.NewEmailMessage(from, to, subject, body)
}

// NewFullEmailMessage creates a new EmailMessage with all fields
func NewFullEmailMessage(from string, to []string, subject string, cc []string, bcc []string, replyTo string, textBody string, htmlBody string, attachments []*Attachment) *EmailMessage {
return shared.NewFullEmailMessage(from, to, subject, cc, bcc, replyTo, textBody, htmlBody, attachments)
// NewEmailMessageWithOptions creates a new EmailMessage with the specified options.
func NewEmailMessageWithOptions(options ...MessageOption) *EmailMessage {
s := EmailMessage{}

for _, option := range options {
option(&s)
}

return &s
}

// NewAttachment creates a new Attachment instance with the specified filename and content
Expand All @@ -47,12 +56,12 @@ func BuildMimeMessage(message *EmailMessage) ([]byte, error) {

// ValidateEmail validates and sanitizes an email address
func ValidateEmail(email string) string {
return shared.ValidateEmail(email)
return shared.ValidateEmailAddress(email)
}

// ValidateEmailSlice validates and sanitizes a slice of email addresses
func ValidateEmailSlice(emails []string) []string {
return shared.ValidateEmailSlice(emails)
return shared.ValidateEmailAddresses(emails)
}

// GetMimeType returns the MIME type based on the file extension
Expand Down
30 changes: 14 additions & 16 deletions newman_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,28 @@ func TestNewEmailMessage(t *testing.T) {
assert.Empty(t, emailMessage.GetHTML())
}

func TestNewFullEmailMessage(t *testing.T) {
func TestNewEmailMessageWithOptions(t *testing.T) {
from := "[email protected]"
to := []string{"[email protected]"}
cc := []string{"[email protected]"}
bcc := []string{"[email protected]"}
replyTo := "[email protected]"
subject := "Look sister, go get yourself a cup of coffee or something"
textBody := "Ill tell you a little secret about ZIP codes - they are meaningless"
htmlBody := "<p>This is a test HTML body.</p>"
attachments := []*Attachment{
NewAttachment("test.txt", []byte("test content")),
}
body := "And three times a week I shall require a cannoli"
html := "<p>And three times a week I shall require a cannoli</p>"

emailMessage := NewFullEmailMessage(from, to, subject, cc, bcc, replyTo, textBody, htmlBody, attachments)
emailMessage := NewEmailMessageWithOptions(
WithFrom(from),
WithTo(to),
WithSubject(subject),
WithText(body),
WithHTML(html),
)

assert.Equal(t, from, emailMessage.GetFrom())
assert.Equal(t, to, emailMessage.GetTo())
assert.Equal(t, cc, emailMessage.GetCC())
assert.Equal(t, bcc, emailMessage.GetBCC())
assert.Equal(t, replyTo, emailMessage.GetReplyTo())
assert.Equal(t, subject, emailMessage.GetSubject())
assert.Equal(t, textBody, emailMessage.GetText())
assert.Equal(t, htmlBody, emailMessage.GetHTML())
assert.Equal(t, attachments, emailMessage.GetAttachments())
assert.Equal(t, body, emailMessage.GetText())
assert.Equal(t, html, emailMessage.GetHTML())
assert.Empty(t, emailMessage.GetBCC())
assert.Empty(t, emailMessage.GetCC())
}

func TestNewAttachment(t *testing.T) {
Expand Down
15 changes: 2 additions & 13 deletions shared/options.go → options.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
package shared
package newman

// Option is a type representing a function that modifies a ResendClient
// MessageOption is a function that sets a field on an EmailMessage
type MessageOption func(*EmailMessage)

// NewResendClient is a function that creates a new ResendClient instance.
func NewEmailMessageWithOptions(options ...MessageOption) *EmailMessage {
s := EmailMessage{}

for _, option := range options {
option(&s)
}

return &s
}

// WithFrom sets the from email address
func WithFrom(from string) MessageOption {
return func(m *EmailMessage) {
Expand Down
1 change: 1 addition & 0 deletions providers/mailgun/mailgun.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type mailgunEmailSender struct {
client mailgun.Mailgun
}

// Option is a type representing a function that modifies a mailgunEmailSender
type Option func(*mailgunEmailSender)

// WithEurope sets the API Mailgun base url to Europe region.
Expand Down
5 changes: 4 additions & 1 deletion providers/postmark/postmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ func (s *postmarkEmailSender) SendEmailWithContext(ctx context.Context, message
return err
}

resp, err := requester.Receive(httpsling.Post(s.endpoint))
resp, err := requester.Receive(
httpsling.Post(s.endpoint),
httpsling.Body(emailStruct),
)
if err != nil {
return ErrFailedToSendEmail
}
Expand Down
5 changes: 5 additions & 0 deletions providers/resend/resend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/resend/resend-go/v2"

"github.com/theopenlane/newman"
"github.com/theopenlane/newman/shared"
)

// resendEmailSender represents a type that is responsible for sending email messages using the Resend service
Expand Down Expand Up @@ -98,6 +99,10 @@ func (s *resendEmailSender) SendEmail(message *newman.EmailMessage) error {

// SendEmailWithContext satisfies the EmailSender interface
func (s *resendEmailSender) SendEmailWithContext(ctx context.Context, message *newman.EmailMessage) error {
if err := shared.ValidateEmailMessage(message); err != nil {
return err
}

msgToSend := resend.SendEmailRequest{
From: message.From,
To: slices.Clone(message.To),
Expand Down
62 changes: 48 additions & 14 deletions providers/resend/resend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,17 @@ func TestSendEmail(t *testing.T) {
emailSender, err := New(apiKey, WithClient(mc))
assert.NoError(t, err)

message := newman.NewEmailMessage("[email protected]", []string{"[email protected]"}, "Test Email", "The air is so dewy sweet you dont even have to lick the stamps").
SetCC([]string{"[email protected]"}).
SetBCC([]string{"[email protected]"}).
SetReplyTo("[email protected]").
SetHTML("<p>The air is so dewy sweet you dont even have to lick the stamps</p>").
SetBCC([]string{"[email protected]"}).
AddAttachment(newman.NewAttachment("test.txt", []byte("When you control the mail, you control… INFORMATION!")))
message := newman.NewEmailMessageWithOptions(
newman.WithFrom("[email protected]"),
newman.WithTo([]string{"[email protected]"}),
newman.WithSubject("Test Email"),
newman.WithText("The air is so dewy sweet you dont even have to lick the stamps"),
newman.WithCc([]string{"[email protected]"}),
newman.WithBcc([]string{"[email protected]"}),
newman.WithReplyTo("[email protected]"),
newman.WithHTML("<p>The air is so dewy sweet you dont even have to lick the stamps</p>"),
newman.WithAttachment(newman.NewAttachment("test.txt", []byte("When you control the mail, you control… INFORMATION!"))),
)

err = emailSender.SendEmailWithContext(context.Background(), message)
assert.NoError(t, err)
Expand All @@ -147,15 +151,45 @@ func TestSendEmailError(t *testing.T) {
emailSender, err := New(apiKey, WithClient(mc))
assert.NoError(t, err)

message := newman.NewEmailMessage("[email protected]", []string{"[email protected]"}, "Test Email", "The air is so dewy sweet you dont even have to lick the stamps").
SetCC([]string{"[email protected]"}).
SetBCC([]string{"[email protected]"}).
SetReplyTo("[email protected]").
SetHTML("<p>The air is so dewy sweet you dont even have to lick the stamps</p>").
SetBCC([]string{"[email protected]"}).
AddAttachment(newman.NewAttachment("test.txt", []byte("When you control the mail, you control… INFORMATION!")))
message := newman.NewEmailMessageWithOptions(
newman.WithFrom("[email protected]"),
newman.WithTo([]string{"[email protected]"}),
newman.WithSubject("Test Email"),
newman.WithText("The air is so dewy sweet you dont even have to lick the stamps"),
newman.WithCc([]string{"[email protected]"}),
newman.WithBcc([]string{"[email protected]"}),
newman.WithReplyTo("[email protected]"),
newman.WithHTML("<p>The air is so dewy sweet you dont even have to lick the stamps</p>"),
newman.WithAttachment(newman.NewAttachment("test.txt", []byte("When you control the mail, you control… INFORMATION!"))),
)

err = emailSender.SendEmailWithContext(context.Background(), message)
assert.Error(t, err)
assert.ErrorIs(t, err, ErrFailedToSendEmail)
}

func TestSendEmailValidatFail(t *testing.T) {
apiKey := "re_send_api_key" // #nosec G101

mc, ts := mockClient(t, apiKey, false)
defer ts.Close()

emailSender, err := New(apiKey, WithClient(mc))
assert.NoError(t, err)

message := newman.NewEmailMessageWithOptions(
newman.WithFrom("[email protected]"),
newman.WithTo([]string{"jerry"}), // invalid email
newman.WithSubject("Test Email"),
newman.WithText("The air is so dewy sweet you dont even have to lick the stamps"),
newman.WithCc([]string{"[email protected]"}),
newman.WithBcc([]string{"[email protected]"}),
newman.WithReplyTo("[email protected]"),
newman.WithHTML("<p>The air is so dewy sweet you dont even have to lick the stamps</p>"),
newman.WithAttachment(newman.NewAttachment("test.txt", []byte("When you control the mail, you control… INFORMATION!"))),
)

err = emailSender.SendEmailWithContext(context.Background(), message)
assert.Error(t, err)
assert.ErrorContains(t, err, "to is required")
}
24 changes: 12 additions & 12 deletions scrubber/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@ Example:
```go
import (
"github.com/theopenlane/newman/scrubber"
"github.com/theopenlane/newman/shared"
"github.com/theopenlane/newman"
"html"
"strings"
)

func main() {
email := shared.NewEmailMessage("[email protected]", []string{"[email protected]"}, "Subject", "<p>HTML content</p>")
email := newman.NewEmailMessage("[email protected]", []string{"[email protected]"}, "Subject", "<p>HTML content</p>")

customTextScrubber := scrubber.ScrubberFunc(func(content string) string {
Implement your custom scrubber logic
return strings.ToLower(strings.TrimSpace(content))
})
customTextScrubber := scrubber.ScrubberFunc(func(content string) string {
//Implement your custom scrubber logic
return strings.ToLower(strings.TrimSpace(content))
})

customHtmlScrubber := scrubber.ScrubberFunc(func(content string) string {
Implement your custom scrubber logic
return html.EscapeString(content)
})
customHtmlScrubber := scrubber.ScrubberFunc(func(content string) string {
//Implement your custom scrubber logic
return html.EscapeString(content)
})

email.SetCustomTextScrubber(customTextScrubber)
email.SetCustomHtmlScrubber(customHtmlScrubber)
email.SetCustomTextScrubber(customTextScrubber)
email.SetCustomHTMLScrubber(customHtmlScrubber)
}
```
10 changes: 5 additions & 5 deletions shared/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (e *EmailMessage) GetFrom() string {
return ""
}

return ValidateEmail(e.From)
return ValidateEmailAddress(e.From)
}

// GetTo returns a slice of trimmed and validated recipient email addresses
Expand All @@ -177,7 +177,7 @@ func (e *EmailMessage) GetTo() []string {
return []string{}
}

return ValidateEmailSlice(e.To)
return ValidateEmailAddresses(e.To)
}

// GetCC returns a slice of trimmed and validated CC recipient email addresses
Expand All @@ -186,7 +186,7 @@ func (e *EmailMessage) GetCC() []string {
return []string{}
}

return ValidateEmailSlice(e.Cc)
return ValidateEmailAddresses(e.Cc)
}

// GetBCC returns a slice of trimmed and validated BCC recipient email addresses
Expand All @@ -195,7 +195,7 @@ func (e *EmailMessage) GetBCC() []string {
return []string{}
}

return ValidateEmailSlice(e.Bcc)
return ValidateEmailAddresses(e.Bcc)
}

// GetReplyTo returns the trimmed and validated reply-to email address
Expand All @@ -204,7 +204,7 @@ func (e *EmailMessage) GetReplyTo() string {
return ""
}

return ValidateEmail(e.ReplyTo)
return ValidateEmailAddress(e.ReplyTo)
}

// GetSubject returns the scrubd email subject
Expand Down
Loading

0 comments on commit bc8679a

Please sign in to comment.