Compare commits

...

2 Commits

Author SHA1 Message Date
Urko. 83ef9c2076 feat: test secure 2023-12-26 09:51:06 +01:00
Urko. 624a269454 refactor: constructor functions 2023-12-26 09:16:21 +01:00
3 changed files with 171 additions and 83 deletions

View File

@ -9,7 +9,7 @@ import (
func main() {
// Here fill with real data
emailService := email.NewMailService(email.MailServiceConfig{
emailService := email.NewInsecure(email.MailServiceConfig{
Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"),
Host: "smtp.youremail.com",
Port: "587",

View File

@ -3,10 +3,13 @@ package email
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"io"
"net/smtp"
"slices"
"time"
)
const (
@ -35,7 +38,7 @@ type EmailService struct {
dial SmtpDialFn
}
func NewMailService(config MailServiceConfig) *EmailService {
func NewInsecure(config MailServiceConfig) *EmailService {
return &EmailService{
auth: config.Auth,
host: config.Host,
@ -49,6 +52,69 @@ func NewMailService(config MailServiceConfig) *EmailService {
}
}
var validCommonNames = []string{"ISRG Root X1", "R3", "DST Root CA X3"}
func NewSecure(config MailServiceConfig) *EmailService {
return &EmailService{
auth: config.Auth,
host: config.Host,
port: config.Port,
from: config.From,
tlsconfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: config.Host,
VerifyConnection: func(cs tls.ConnectionState) error {
// // Check the server's common name
// for _, cert := range cs.PeerCertificates {
// log.Println("cert.DNSNames", cert.DNSNames)
// if err := cert.VerifyHostname(config.Host); err != nil {
// return fmt.Errorf("invalid common name: %w", err)
// }
// }
// Check the certificate chain
opts := x509.VerifyOptions{
Intermediates: x509.NewCertPool(),
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := cs.PeerCertificates[0].Verify(opts)
if err != nil {
return fmt.Errorf("invalid certificate chain: %w", err)
}
// Iterate over the certificates again to perform custom checks
for _, cert := range cs.PeerCertificates {
// TODO: add more checks here...
if time.Now().After(cert.NotAfter) {
return fmt.Errorf("certificate has expired")
}
if time.Now().Add(30 * 24 * time.Hour).After(cert.NotAfter) {
return fmt.Errorf("certificate will expire within 30 days")
}
if !slices.Contains(validCommonNames, cert.Issuer.CommonName) {
return fmt.Errorf("certificate is not issued by a trusted CA")
}
// log.Println("cert.ExtKeyUsage", cert.ExtKeyUsage)
// if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 || len(cert.ExtKeyUsage) == 0 || !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) {
// log.Printf("%+v", cert)
// return fmt.Errorf("certificate cannot be used for server authentication")
// }
if cert.PublicKeyAlgorithm != x509.RSA {
return fmt.Errorf("unsupported public key algorithm")
}
}
return nil
},
},
dial: dial,
}
}
type MailServiceConfig struct {
Auth smtp.Auth
Host string

View File

@ -57,106 +57,128 @@ func TestMockSendEmail(t *testing.T) {
}
}
func TestSendEmail(t *testing.T) {
func TestNewInsecure(t *testing.T) {
cfg := newConfig(".env.test")
mailSrv := NewMailService(MailServiceConfig{
mailSrv := NewInsecure(MailServiceConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
})
data := EmailMessage{
To: cfg.MailTo,
Subject: "Mail Sender",
Body: "Hello this is a test email",
}
require.NoError(t, mailSrv.SendEmail(data))
}
func TestSendEmail_FailedAuthentication(t *testing.T) {
cfg := newConfig(".env.test")
// set up authentication to fail
mailSrv := NewMailService(MailServiceConfig{
Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost),
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
t.Run("TestSendEmail", func(t *testing.T) {
data := EmailMessage{
To: cfg.MailTo,
Subject: "Mail Sender",
Body: "Hello this is a test email",
}
require.NoError(t, mailSrv.SendEmail(data))
})
t.Run("TestSendEmailWithAttachments", func(t *testing.T) {
reader, err := os.Open("testdata/attachment1.txt")
require.NoError(t, err)
defer reader.Close()
reader2, err := os.Open("testdata/attachment2.txt")
require.NoError(t, err)
defer reader2.Close()
reader3, err := os.Open("testdata/attachment3.txt")
require.NoError(t, err)
defer reader3.Close()
data := EmailMessage{
To: cfg.MailTo,
Subject: "Mail Sender",
Body: "Hello this is a test email",
Attachments: []EmailAttachment{
{
Title: "attachment1.txt",
File: reader,
},
{
Title: "attachment2.txt",
File: reader2,
},
{
Title: "attachment3.txt",
File: reader3,
},
},
}
err = mailSrv.SendEmail(data)
require.NoError(t, err)
})
t.Run("TestWithAttachments", func(t *testing.T) {
msg := newMessage("from", "to", "subject")
content, err := msg.withAttachments("body", nil)
require.NoError(t, err)
assert.Greater(t, len(content), 0)
})
t.Run("TestSendEmail_InvalidRecipient", func(t *testing.T) {
data := EmailMessage{
To: "invalid_email",
Subject: "Test Email",
Body: "This is a test email.",
}
err := mailSrv.SendEmail(data)
assert.Error(t, err)
})
t.Run("TestSendEmail_FailedAuthentication", func(t *testing.T) {
// set up authentication to fail
mailSrv := NewInsecure(MailServiceConfig{
Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost),
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
})
data := EmailMessage{
To: cfg.MailTo,
Subject: "Test Email",
Body: "This is a test email.",
}
err := mailSrv.SendEmail(data)
assert.Error(t, err)
})
data := EmailMessage{
To: cfg.MailTo,
Subject: "Test Email",
Body: "This is a test email.",
}
err := mailSrv.SendEmail(data)
assert.Error(t, err)
}
func TestSendEmail_InvalidRecipient(t *testing.T) {
func TestSecure(t *testing.T) {
cfg := newConfig(".env.test")
mailSrv := NewMailService(MailServiceConfig{
emailService := NewSecure(MailServiceConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
})
data := EmailMessage{
To: "invalid_email",
Subject: "Test Email",
Body: "This is a test email.",
}
err := mailSrv.SendEmail(data)
assert.Error(t, err)
}
func TestSendEmailWithAttachments(t *testing.T) {
cfg := newConfig(".env.test")
// Assert that the tls.Config is set up correctly
assert.NotNil(t, emailService.tlsconfig)
assert.True(t, emailService.tlsconfig.InsecureSkipVerify)
assert.Equal(t, cfg.MailHost, emailService.tlsconfig.ServerName)
assert.NotNil(t, emailService.tlsconfig.VerifyConnection)
mailSrv := NewMailService(MailServiceConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
t.Run("TestSendEmail", func(t *testing.T) {
// Mock the client and test the StartTLS method
var called bool
mockDialFn := func(hostPort string) (SMTPClientIface, error) {
called = true
return &mockSMTP{}, nil
}
emailService.dial = mockDialFn
data := EmailMessage{
To: cfg.MailTo,
Subject: "Mail Sender",
Body: "Hello this is a test email",
}
require.NoError(t, emailService.SendEmail(data))
assert.Equal(t, true, called)
})
reader, err := os.Open("testdata/attachment1.txt")
require.NoError(t, err)
defer reader.Close()
reader2, err := os.Open("testdata/attachment2.txt")
require.NoError(t, err)
defer reader2.Close()
reader3, err := os.Open("testdata/attachment3.txt")
require.NoError(t, err)
defer reader3.Close()
data := EmailMessage{
To: cfg.MailTo,
Subject: "Mail Sender",
Body: "Hello this is a test email",
Attachments: []EmailAttachment{
{
Title: "attachment1.txt",
File: reader,
},
{
Title: "attachment2.txt",
File: reader2,
},
{
Title: "attachment3.txt",
File: reader3,
},
},
}
err = mailSrv.SendEmail(data)
require.NoError(t, err)
}
func TestWithAttachments(t *testing.T) {
msg := newMessage("from", "to", "subject")
content, err := msg.withAttachments("body", nil)
require.NoError(t, err)
assert.Greater(t, len(content), 0)
}