Compare commits

..

2 Commits

Author SHA1 Message Date
$(pass /github/name)
6ac2b32a1f add: plaintext email; improved templating 2024-08-28 11:35:43 +02:00
$(pass /github/name)
00ca11d2e6 Add: Security Headers 2024-08-28 02:30:38 +02:00
12 changed files with 304 additions and 116 deletions

1
.gitignore vendored
View File

@@ -36,6 +36,7 @@ go.work
!*.go
!*.html
!*.tmpl
!*.css
!go.sum
!go.mod

View File

@@ -1,5 +1,7 @@
{
"WebsiteTitle": "My Carsharing Site",
"BaseURL": "https://domain.de",
"Environment": "dev",
"db": {
"Path": "data/db.sqlite3"
},
@@ -8,13 +10,13 @@
"User": "username",
"Password": "password",
"Port": 465,
"Mailtype": "html",
"AdminEmail": "admin@server.com"
},
"templates": {
"MailPath": "templates/email",
"HTMLPath": "templates/html",
"StaticPath": "templates/css"
"StaticPath": "templates/css",
"LogoURI": "/images/LOGO.png"
},
"auth": {
"APIKey": ""

View File

@@ -33,7 +33,6 @@ type SMTPConfig struct {
Host string `json:"Host" envconfig:"SMTP_HOST"`
User string `json:"User" envconfig:"SMTP_USER"`
Password string `json:"Password" envconfig:"SMTP_PASS"`
Mailtype string `json:"Mailtype" default:"html" envconfig:"MAIL_TYPE"`
AdminEmail string `json:"AdminEmail" envconfig:"ADMIN_MAIL"`
Port int `json:"Port" default:"465" envconfig:"SMTP_PORT"`
}
@@ -42,6 +41,7 @@ type TemplateConfig struct {
MailPath string `json:"MailPath" default:"templates/email" envconfig:"TEMPLATE_MAIL_PATH"`
HTMLPath string `json:"HTMLPath" default:"templates/html" envconfig:"TEMPLATE_HTML_PATH"`
StaticPath string `json:"StaticPath" default:"templates/css" envconfig:"TEMPLATE_STATIC_PATH"`
LogoURI string `json:"LogoURI" envconfig:"LOGO_URI"`
}
type RecipientsConfig struct {
@@ -60,6 +60,7 @@ type Config struct {
Templates TemplateConfig `json:"templates"`
Recipients RecipientsConfig `json:"recipients"`
ConfigFilePath string `json:"config_file_path" envconfig:"CONFIG_FILE_PATH"`
WebsiteTitle string `json:"WebsiteTitle" envconfig:"WEBSITE_TITLE"`
BaseURL string `json:"BaseUrl" envconfig:"BASE_URL"`
Env string `json:"Environment" default:"development" envconfig:"ENV"`
DB DatabaseConfig `json:"db"`
@@ -68,16 +69,17 @@ type Config struct {
}
var (
BaseURL string
CFGPath string
CFG Config
Auth AuthenticationConfig
DB DatabaseConfig
Templates TemplateConfig
SMTP SMTPConfig
Recipients RecipientsConfig
Env string
Security SecurityConfig
BaseURL string
WebsiteTitle string
CFGPath string
CFG Config
Auth AuthenticationConfig
DB DatabaseConfig
Templates TemplateConfig
SMTP SMTPConfig
Recipients RecipientsConfig
Env string
Security SecurityConfig
)
var environmentOptions map[string]bool = map[string]bool{
"development": true,
@@ -117,6 +119,7 @@ func LoadConfig() {
Recipients = CFG.Recipients
Security = CFG.Security
Env = CFG.Env
WebsiteTitle = CFG.WebsiteTitle
logger.Info.Printf("Config loaded: %#v", CFG)
}

View File

@@ -128,6 +128,12 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error {
if !strings.Contains(message.Body, config.BaseURL) {
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL)
}
if !strings.Contains(message.Body, config.BaseURL+config.Templates.LogoURI) {
return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.BaseURL+config.WebsiteTitle)
}
if !strings.Contains(message.Body, config.WebsiteTitle) {
return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.WebsiteTitle)
}
return nil
}

View File

@@ -0,0 +1,16 @@
package middlewares
import "github.com/gin-gonic/gin"
func SecurityHeadersMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Feature-Policy", "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
c.Header("Permissions-Policy", "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()")
c.Next()
}
}

View File

@@ -55,6 +55,7 @@ func Run() {
router.Use(gin.Logger())
router.Use(middlewares.CORSMiddleware())
router.Use(middlewares.CSPMiddleware())
router.Use(middlewares.SecurityHeadersMiddleware())
limiter := middlewares.NewIPRateLimiter(config.Security.Ratelimits.Limit, config.Security.Ratelimits.Burst)
router.Use(middlewares.RateLimitMiddleware(limiter))

View File

@@ -74,7 +74,7 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
}
subject := constants.MailVerificationSubject
body, err := ParseTemplate("mail_verification.html", data)
body, err := ParseTemplate("mail_verification.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't send verification mail")
return err
@@ -92,6 +92,8 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
BASEURL string
MembershipID int64
MembershipFee float32
Logo string
WebsiteTitle string
RentalFee float32
}{
Company: user.Company,
@@ -101,10 +103,12 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
BASEURL: config.BaseURL,
WebsiteTitle: config.WebsiteTitle,
Logo: config.Templates.LogoURI,
}
subject := constants.MailWelcomeSubject
body, err := ParseTemplate("mail_welcome.html", data)
body, err := ParseTemplate("mail_welcome.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't send welcome mail")
return err
@@ -130,6 +134,8 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
MembershipID int64
RentalFee float32
MembershipFee float32
Logo string
WebsiteTitle string
}{
Company: user.Company,
FirstName: user.FirstName,
@@ -146,10 +152,12 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
Phone: user.Phone,
IBAN: user.BankAccount.IBAN,
BASEURL: config.BaseURL,
Logo: config.Templates.LogoURI,
WebsiteTitle: config.WebsiteTitle,
}
subject := constants.MailRegistrationSubject
body, err := ParseTemplate("mail_registration.html", data)
body, err := ParseTemplate("mail_registration.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't send admin notification mail")
return err
@@ -159,16 +167,20 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
func (s *EmailService) RelayContactFormMessage(sender string, name string, message string) error {
data := struct {
Message string
Name string
BASEURL string
Message string
Name string
BASEURL string
Logo string
WebsiteTitle string
}{
Message: message,
Name: name,
BASEURL: config.BaseURL,
Message: message,
Name: name,
BASEURL: config.BaseURL,
Logo: config.Templates.LogoURI,
WebsiteTitle: config.WebsiteTitle,
}
subject := constants.MailContactSubject
body, err := ParseTemplate("mail_contact_form.html", data)
body, err := ParseTemplate("mail_contact_form.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't send contact form message mail")
return err

View File

@@ -1,89 +0,0 @@
<!doctype html>
<html>
<body>
<div
style="
background-color: #f2f5f7;
color: #242424;
font-family: &quot;Helvetica Neue&quot;, &quot;Arial Nova&quot;,
&quot;Nimbus Sans&quot;, Arial, sans-serif;
font-size: 16px;
font-weight: 400;
letter-spacing: 0.15008px;
line-height: 1.5;
margin: 0;
padding: 32px 0;
min-height: 100%;
width: 100%;
"
>
<table
align="center"
width="100%"
style="margin: 0 auto; max-width: 600px; background-color: #ffffff"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
>
<tbody>
<tr style="width: 100%">
<td>
<div style="padding: 0px 24px 0px 24px">
<a
href="https://carsharing-hasloh.de"
style="text-decoration: none"
target="_blank"
><img
alt="Carsharing-Hasloh"
src="https://carsharing-hasloh.de/images/CarsharingSH-Hasloh-LOGO.jpeg"
style="
outline: none;
border: none;
text-decoration: none;
vertical-align: middle;
display: inline-block;
max-width: 100%;
"
/></a>
</div>
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
Moin Du Vorstand 👋,
</div>
<div style="font-weight: normal; padding: 0px 24px 16px 24px">
<p>Eine neue Kontaktanfrage<br />{{.Name}} hat geschrieben</p>
</div>
<div
style="
font-size: 22px;
font-weight: normal;
text-align: center;
padding: 0px 24px 0px 24px;
"
>
Hier ist die Nachricht:
</div>
<div style="font-weight: normal; padding: 16px 24px 0px 24px">
<p>{{.Message}}</p>
</div>
<div style="padding: 16px 0px 16px 0px">
<hr
style="
width: 100%;
border: none;
border-top: 1px solid #cccccc;
margin: 0;
"
/>
</div>
<div style="font-weight: normal; padding: 16px 24px 16px 24px">
<p>Mit freundlichen Grüßen,</p>
<p>Dein untertänigster Wolkenrechner</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,127 @@
Moin Du Vorstand 👋,
Eine neue Kontaktanfrage!
{{.Name}} hat geschrieben
Hier ist die Nachricht:
{{.Message}}
Mit freundlichen Grüßen,
Dein untertänigster Wolkenrechner
<!doctype html>
<html>
<body>
<div
style="
background-color: #f2f5f7;
color: #242424;
font-family: &quot;Helvetica Neue&quot;, &quot;Arial Nova&quot;,
&quot;Nimbus Sans&quot;, Arial, sans-serif;
font-size: 16px;
font-weight: 400;
letter-spacing: 0.15008px;
line-height: 1.5;
margin: 0;
padding: 32px 0;
min-height: 100%;
width: 100%;
"
>
<table
align="center"
width="100%"
style="
margin: 0 auto;
max-width: 600px;
background-color: #ffffff;
"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
>
<tbody>
<tr style="width: 100%">
<td>
<div style="padding: 0px 24px 0px 24px">
<a
href="{{.BASEURL}}"
style="text-decoration: none"
target="_blank"
><img
alt="{{.WebsiteTitle}}"
src="{{.BASEURL}}{{.Logo}}"
style="
outline: none;
border: none;
text-decoration: none;
vertical-align: middle;
display: inline-block;
max-width: 100%;
"
/></a>
</div>
<div
style="
font-weight: normal;
padding: 0px 24px 16px 24px;
"
>
Moin Du Vorstand 👋,
</div>
<div
style="
font-weight: normal;
padding: 0px 24px 16px 24px;
"
>
<p>
Eine neue Kontaktanfrage<br />{{.Name}} hat
geschrieben
</p>
</div>
<div
style="
font-size: 22px;
font-weight: normal;
text-align: center;
padding: 0px 24px 0px 24px;
"
>
Hier ist die Nachricht:
</div>
<div
style="
font-weight: normal;
padding: 16px 24px 0px 24px;
"
>
<p>{{.Message}}</p>
</div>
<div style="padding: 16px 0px 16px 0px">
<hr
style="
width: 100%;
border: none;
border-top: 1px solid #cccccc;
margin: 0;
"
/>
</div>
<div
style="
font-weight: normal;
padding: 16px 24px 16px 24px;
"
>
<p>Mit freundlichen Grüßen,</p>
<p>Dein untertänigster Wolkenrechner</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -1,3 +1,32 @@
Moin Du Vorstand 👋,
Ein neues Mitglied!!!<br />{{.FirstName}} {{.LastName}} hat sich registriert.
Hier sind die Daten:
---------------------
Das gebuchtes Modell:
Name: {{.MembershipModel}}
Preis/Monat: {{.MembershipFee}}
Preis/h: {{.RentalFee}}
Persönliche Daten:
{{if .Company}}
Firma: {{.Company}}
{{end}}
Name: {{.FirstName}} {{.LastName}}
Mitgliedsnr: {{.MembershipID}}
Adresse: {{.Address}},
{{.ZipCode}} {{.City}}
Geburtsdatum: {{.DateOfBirth}}
Email: {{.Email}}
Telefon: {{.Phone}}
IBAN: {{.IBAN}}
Mit freundlichen Grüßen,
Dein untertänigster Wolkenrechner
<!doctype html>
<html>
<body>
@@ -35,8 +64,8 @@
style="text-decoration: none"
target="_blank"
><img
alt="Carsharing-Hasloh"
src="{{.BASEURL}}/images/CarsharingSH-Hasloh-LOGO.jpeg"
alt="{{.WebsiteTitle}}"
src="{{.BASEURL}}{{.Logo}}"
style="
outline: none;
border: none;

View File

@@ -1,3 +1,27 @@
Moin {{.FirstName}} {{.LastName}} 👋,
herzlich willkommen beim Dörpsmobil Hasloh e.V.! Vielen Dank für
Ihre Registrierung und Ihre Unterstützung unseres Projekts.
Um die Registrierung abschließen zu können bestätigen Sie bitte
noch Ihre Emailadresse indem Sie hier klicken:
E-Mail Adresse bestätigen
{{.BASEURL}}/backend/verify?token={{.Token}}
Nachdem wir Ihre E-Mail Adresse bestätigen konnten, schicken wir
Ihnen alle weiteren Informationen zu. Wir freuen uns auf die
gemeinsame Zeit mit Ihnen!
Sollte es Probleme geben, möchten wir uns gerne jetzt schon
dafür entschuldigen, wenden Sie sich gerne an uns, wir werden
uns sofort darum kümmern, versprochen! Antworten Sie einfach auf
diese E-Mail.
Mit Freundlichen Grüßen,
Der Vorstand
<!doctype html>
<html>
<body>

View File

@@ -1,3 +1,59 @@
Moin {{.FirstName}} {{if .Company}}({{.Company}}){{end}}👋,
wir freuen uns sehr, dich als neues Mitglied bei Carsharing
Hasloh begrüßen zu dürfen! Herzlichen Glückwunsch zur
erfolgreichen E-Mail-Verifikation und willkommen in unserem
Verein!
Hier einige wichtige Informationen für dich:
Deine Mitgliedsnummer: {{.MembershipID}}
Dein gebuchtes Modell:
Name: {{.MembershipModel}}
Preis/Monat: {{.MembershipFee}}
Preis/h: {{.RentalFee}}
Mitgliedsbeitrag: Solange wir noch kein
Fahrzeug im Betrieb haben, zahlst Du sinnvollerweise auch
keinen Mitgliedsbeitrag. Es ist zur Zeit der 1.1.2025 als
Startdatum geplant.
Führerscheinverifikation: Weitere Informationen zur Verifikation
deines Führerscheins folgen in Kürze. Du musst nichts weiter tun,
wir werden uns bei dir melden, sobald es notwendig ist.
Moqo App:
Wir werden die Moqo App nutzen,
um das Fahrzeug ausleihen zu können. Wenn Du schon mal einen
ersten Eindruck von dem Buchungsvorgang haben möchtest,
schaue Dir gerne dieses kurze Video an:
Moqo App Nutzung
https://www.youtube.com/shorts/ZMKUX0uyOps
Dörpsmobil:
Wir sind nicht alleine sondern Mitglied in einem Schleswig-Holstein
weiten Netz an gemeinnützigen Carsharing Anbietern. Für mehr
Informationen zu diesem Netzwerk haben wir auch ein Video vorbereitet:
Dörpsmobil SH
https://www.youtube.com/watch?v=NSch-2F-ru0
Für mehr Informationen besuche gerne unsere Webseite:
Carsharing-Hasloh.de
{{.BASEURL}}
Solltest du Fragen haben oder Unterstützung benötigen, kannst
du dich jederzeit an unsere Vorsitzende wenden:
Anke Freitag
E-Mail: vorstand@carsharing-hasloh.de
Telefon: +49 176 5013 4256
Wir danken dir herzlich für dein Vertrauen in uns und freuen uns
darauf, dich hoffentlich bald mit einem Auto begrüßen zu dürfen.
Mit freundlichen Grüßen,
Dein Carsharing Hasloh Team
<!doctype html>
<html>
<body>
@@ -35,8 +91,8 @@
style="text-decoration: none"
target="_blank"
><img
alt="Carsharing Hasloh"
src="{{.BASEURL}}/images/CarsharingSH-Hasloh-LOGO.jpeg"
alt="{{.WebsiteTitle}}"
src="{{.BASEURL}}{{.Logo}}"
style="
outline: none;
border: none;