refactor: constructor functions

This commit is contained in:
Urko. 2023-12-26 09:16:21 +01:00
parent 1371889d6d
commit 624a269454
3 changed files with 141 additions and 89 deletions

View File

@ -9,7 +9,7 @@ import (
func main() { func main() {
// Here fill with real data // 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"), Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"),
Host: "smtp.youremail.com", Host: "smtp.youremail.com",
Port: "587", Port: "587",

View File

@ -3,10 +3,13 @@ package email
import ( import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"net/smtp" "net/smtp"
"slices"
"time"
) )
const ( const (
@ -35,7 +38,7 @@ type EmailService struct {
dial SmtpDialFn dial SmtpDialFn
} }
func NewMailService(config MailServiceConfig) *EmailService { func NewInsecure(config MailServiceConfig) *EmailService {
return &EmailService{ return &EmailService{
auth: config.Auth, auth: config.Auth,
host: config.Host, 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 { type MailServiceConfig struct {
Auth smtp.Auth Auth smtp.Auth
Host string Host string

View File

@ -57,106 +57,92 @@ func TestMockSendEmail(t *testing.T) {
} }
} }
func TestSendEmail(t *testing.T) { func TestNewInsecure(t *testing.T) {
cfg := newConfig(".env.test") cfg := newConfig(".env.test")
mailSrv := NewMailService(MailServiceConfig{ mailSrv := NewInsecure(MailServiceConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost, Host: cfg.MailHost,
Port: cfg.MailPort, Port: cfg.MailPort,
From: cfg.MailFrom, From: cfg.MailFrom,
}) })
data := EmailMessage{ t.Run("TestSendEmail", func(t *testing.T) {
To: cfg.MailTo, data := EmailMessage{
Subject: "Mail Sender", To: cfg.MailTo,
Body: "Hello this is a test email", Subject: "Mail Sender",
} Body: "Hello this is a test email",
require.NoError(t, mailSrv.SendEmail(data)) }
} 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,
}) })
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) { t.Run("TestSendEmailWithAttachments", func(t *testing.T) {
cfg := newConfig(".env.test") reader, err := os.Open("testdata/attachment1.txt")
mailSrv := NewMailService(MailServiceConfig{ require.NoError(t, err)
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), defer reader.Close()
Host: cfg.MailHost,
Port: cfg.MailPort, reader2, err := os.Open("testdata/attachment2.txt")
From: cfg.MailFrom, 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)
}) })
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) { t.Run("TestWithAttachments", func(t *testing.T) {
cfg := newConfig(".env.test") msg := newMessage("from", "to", "subject")
content, err := msg.withAttachments("body", nil)
mailSrv := NewMailService(MailServiceConfig{ require.NoError(t, err)
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), assert.Greater(t, len(content), 0)
Host: cfg.MailHost,
Port: cfg.MailPort,
From: cfg.MailFrom,
}) })
reader, err := os.Open("testdata/attachment1.txt")
require.NoError(t, err)
defer reader.Close()
reader2, err := os.Open("testdata/attachment2.txt") t.Run("TestSendEmail_InvalidRecipient", func(t *testing.T) {
require.NoError(t, err) data := EmailMessage{
defer reader2.Close() To: "invalid_email",
Subject: "Test Email",
Body: "This is a test email.",
}
err := mailSrv.SendEmail(data)
assert.Error(t, err)
})
reader3, err := os.Open("testdata/attachment3.txt") t.Run("TestSendEmail_FailedAuthentication", func(t *testing.T) {
require.NoError(t, err) // set up authentication to fail
defer reader3.Close() mailSrv := NewInsecure(MailServiceConfig{
Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost),
data := EmailMessage{ Host: cfg.MailHost,
To: cfg.MailTo, Port: cfg.MailPort,
Subject: "Mail Sender", From: cfg.MailFrom,
Body: "Hello this is a test email", })
Attachments: []EmailAttachment{ data := EmailMessage{
{ To: cfg.MailTo,
Title: "attachment1.txt", Subject: "Test Email",
File: reader, Body: "This is a test email.",
}, }
{ err := mailSrv.SendEmail(data)
Title: "attachment2.txt", assert.Error(t, err)
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)
} }