improved sendmail + docs

This commit is contained in:
Rodrigue Chakode 2020-05-14 14:58:22 +02:00
parent 9755f2af55
commit 14d5acf5e8
7 changed files with 139 additions and 66 deletions

View File

@ -19,7 +19,7 @@ all: test build
deploy:
which gcloud
gcloud components install app-engine-go
gcloud app deploy
gcloud app deploy --quiet
build:
$(GOBUILD) -o $(PACKAGE_NAME) -v

View File

@ -3,24 +3,85 @@
# Configuration variables
* Create the App Engine configuration file
```
cp app.yaml.sample app.yaml
```
* Edit the `app.yaml` file with your favorite editor and set the following environement variables appropriately:
```
SERVER_ADDR=":8393"
SMTP_SERVER_ADDR="mail.example.com:465"
SMTP_CLIENT_USERNAME="demo@example.com"
SMTP_CLIENT_PASSWORD="dem0ToBeChanged"
CONTACT_REPLY_EMAIL="noreply@example.com"
CONTACT_REPLY_CC_EMAIL="contact@example.com"
DEMO_URL="https://demo.example.com/"
ALLOWED_ORIGIN_DOMAIN="localhost:1313"
TEMPLATE_DEMO_REQUEST_REPLY=templates/template_demo_request_reply.html
TEMPLATE_CONTACT_REQUEST_REPLY=templates/template_contact_request_reply.html
SMTP_SERVER_ADDR: "smtp.mailgun.org:587"
SMTP_VERITY_CERT: true
SMTP_CLIENT_USERNAME: "postmaster@example.com"
SMTP_CLIENT_PASSWORD: "postmasterSecretPassWord"
CONTACT_REPLY_EMAIL: "noreply@example.com"
CONTACT_REPLY_CC_EMAIL: "contact@example.com"
DEMO_URL: "https://demo.example.com/"
ALLOWED_ORIGIN_DOMAIN: "example.com"
```
## Required HTTP Headers
* `Origin`
* `Referer`
## SMTP
https://cloud.google.com/compute/docs/tutorials/sending-mail/using-mailgun?hl=fr
## Test
```
curl -H'Origin: http://realopinsight.com' \
-H'Referer: realopinsight.com' \
-H'Content-Type: application/x-www-form-urlencoded' \
-d 'target=contact' \
-XPOST https://hugo-mx-gateway.ew.r.appspot.com/sendmail
```
# Build
```sh
make build
```
# Hugo Contact Form
See `./model/hugo-contact-form.html`.
```
<div id="reply-message"></div>
<div>
<fieldset>
<legend>Please fill in the form to submit your request</legend>
<form action="https://contact-request-endpoint/" method="post">
<div class="form-item">
<label for="name">Name</label>
<input type="text" name="name" id="name" placeholder="Mr. Smith" />
</div>
<div class="form-item">
<label for="email">Email <span class="req"></span></label>
<input type="text" name="email" id="email" class="required email" placeholder="smith@company.com" />
</div>
<div class="form-item">
<label for="organization">Organization</label>
<input type="text" name="organization" id="organization" placeholder="Company, Inc." />
</div>
{{ if in .Params.tags "contact" }}
<div class="form-item">
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject" value="" placeholder="Need help or expertise?" />
<input type="hidden" name="target" id="target" value="contact" />
</div>
<div class="form-item">
<label for="message">Message</label>
<textarea rows="6" name="message" id="message" placeholder="Please add details concerning your request."></textarea>
</div>
{{ else }}
<div class="form-item">
<input type="hidden" name="subject" id="subject" value="Your Access to Product Demo!" />
<input type="hidden" name="target" id="target" value="demo" />
</div>
{{ end }}
<input class="button" type="submit" value="Submit">
</form>
</fieldset>
</div>
```

View File

@ -5,9 +5,7 @@ env_variables:
SMTP_VERITY_CERT: true
SMTP_CLIENT_USERNAME: "postmaster@example.com"
SMTP_CLIENT_PASSWORD: "postmasterSecretPassWord"
ALLOWED_ORIGINS: "127.0.0.1,example.com"
CONTACT_REPLY_EMAIL: "noreply@example.com"
CONTACT_REPLY_CC_EMAIL: "contact@example.com"
DEMO_URL: "https://demo.example.com/"
ALLOWED_ORIGIN_DOMAIN: "example.com"
TEMPLATE_DEMO_REQUEST_REPLY: templates/template_reply_demo_request.html
TEMPLATE_CONTACT_REQUEST_REPLY: templates/template_reply_contact_request.html
DEMO_URL: "https://demo.example.com/"

View File

@ -1,28 +0,0 @@
<form action="http://example.com/v1/sendemail" method="post">
<div class="form-group">
<label for="name">Name</label>
<input type="text" name="name" id="name" placeholder="Mr. Smith" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" name="email" id="email" placeholder="smith@company.co" />
</div>
<div class="form-group">
<label for="organization">Organization</label>
<input type="text" name="organization" id="organization" placeholder="Company, Inc." />
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject" value="Request subject" />
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea name="message" id="message" placeholder="type your request here"></textarea>
</div>
<input type="submit" value="Send">
</form>

16
main.go
View File

@ -18,7 +18,8 @@ package main
import (
"net/http"
"time"
"time"
"os"
"github.com/spf13/viper"
"github.com/gorilla/mux"
@ -76,7 +77,6 @@ func NewRouter() *mux.Router {
func main() {
viper.AutomaticEnv()
viper.SetDefault("SERVER_ADDR", ":8080")
viper.SetDefault("SERVER_TLS_CERT", "/etc/cert/cert.pem")
viper.SetDefault("SERVER_TLS_PRIVATEKEY", "/etc/cert/privkey.pem")
viper.SetDefault("SMTP_SERVER_ADDR", "127.0.0.1:465")
@ -87,13 +87,17 @@ func main() {
viper.SetDefault("EMAIL_SUBJECT", "Thanks to try our product")
viper.SetDefault("DEMO_URL", "http://company.com/product-demo")
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Infof("Defaulting to port %s", port)
}
router := NewRouter()
serverAddr := viper.GetString("SERVER_ADDR")
log.Infof("Listening on port %s", port)
log.Infof("Listening on %s", serverAddr)
log.Fatal(http.ListenAndServe(serverAddr, router))
log.Fatal(http.ListenAndServe(":"+port, router))
}

View File

@ -0,0 +1,37 @@
<div id="reply-message"></div>
<div>
<fieldset>
<legend>Please fill in the form to submit your request</legend>
<form action="https://contact-request-endpoint/" method="post">
<div class="form-item">
<label for="name">Name</label>
<input type="text" name="name" id="name" placeholder="Mr. Smith" />
</div>
<div class="form-item">
<label for="email">Email <span class="req"></span></label>
<input type="text" name="email" id="email" class="required email" placeholder="smith@company.com" />
</div>
<div class="form-item">
<label for="organization">Organization</label>
<input type="text" name="organization" id="organization" placeholder="Company, Inc." />
</div>
{{ if in .Params.tags "contact" }}
<div class="form-item">
<label for="subject">Subject</label>
<input type="text" name="subject" id="subject" value="" placeholder="Need help or expertise?" />
<input type="hidden" name="target" id="target" value="contact" />
</div>
<div class="form-item">
<label for="message">Message</label>
<textarea rows="6" name="message" id="message" placeholder="Please add details concerning your request."></textarea>
</div>
{{ else }}
<div class="form-item">
<input type="hidden" name="subject" id="subject" value="Your Access to Product Demo!" />
<input type="hidden" name="target" id="target" value="demo" />
</div>
{{ end }}
<input class="button" type="submit" value="Submit">
</form>
</fieldset>
</div>

View File

@ -22,12 +22,12 @@ import (
"encoding/json"
"fmt"
"html/template"
"log"
"net"
"net/http"
"net/smtp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
@ -79,7 +79,7 @@ func (m *SendMailRequest) Execute() error {
// TLS config
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
InsecureSkipVerify: viper.GetBool("SMTP_VERITY_CERT"),
ServerName: smtpServerHost,
}
@ -151,24 +151,25 @@ func (m *SendMailRequest) ParseTemplate(templateFileName string, data interface{
// SendMail handles HTTP request to send email
func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) {
AllowedOriginDomain := viper.GetString("ALLOWED_ORIGIN_DOMAIN")
AllowedOrigins := map[string]bool{
fmt.Sprintf("http://%s", AllowedOriginDomain): true,
fmt.Sprintf("https://%s", AllowedOriginDomain): true,
fmt.Sprintf("http://www.%s", AllowedOriginDomain): true,
fmt.Sprintf("https://www.%s", AllowedOriginDomain): true,
allowedDomains := strings.Split(viper.GetString("ALLOWED_ORIGINS"), ",")
allowedOrigins := make(map[string]bool)
for _, domain := range allowedDomains {
domainTrimmed := strings.TrimSpace(domain)
allowedOrigins[fmt.Sprintf("http://%s", domainTrimmed)] = true
allowedOrigins[fmt.Sprintf("https://%s", domainTrimmed)] = true
allowedOrigins[fmt.Sprintf("http://www.%s", domainTrimmed)] = true
allowedOrigins[fmt.Sprintf("https://www.%s", domainTrimmed)] = true
}
if len(httpReq.Header["Origin"]) == 0 || len(httpReq.Header["Referer"]) == 0 {
rawHeader, _ := json.Marshal(httpReq.Header)
log.Println("request with unexpected headers:", string(rawHeader))
log.Infoln("request with unexpected headers", string(rawHeader))
httpResp.WriteHeader(http.StatusForbidden)
return
}
reqOrigin := httpReq.Header["Origin"][0]
if _, domainFound := AllowedOrigins[reqOrigin]; !domainFound {
log.Println("Not allowed origin:", reqOrigin)
if _, domainFound := allowedOrigins[reqOrigin]; !domainFound {
log.Errorln("not allowed origin", reqOrigin)
httpResp.WriteHeader(http.StatusForbidden)
return
}
@ -191,13 +192,13 @@ func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) {
case "contact":
recipients = []string{viper.GetString("CONTACT_REPLY_CC_EMAIL")}
default:
log.Println("Not allowed request type:", contactRequest.RequestTarget)
log.Infoln("not allowed request type:", contactRequest.RequestTarget)
httpResp.WriteHeader(http.StatusForbidden)
return
}
userData, _ := json.Marshal(contactRequest)
log.Println("New Request:", string(userData))
log.Infoln("New Request:", string(userData))
templateData := struct {
Name string
@ -233,7 +234,7 @@ func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) {
if err == nil {
err := sendMailReq.Execute()
if err != nil {
log.Printf("error: %s", err.Error())
log.Infof("error: %s", err.Error())
contactResponse.Status = "error"
contactResponse.Message = fmt.Sprintf("An internal error occurred, please try later or send us an email at %s.", viper.GetString("CONTACT_REPLY_CC_EMAIL"))
} else {
@ -245,7 +246,7 @@ func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) {
}
}
} else {
log.Printf("error: %s", err.Error())
log.Infof("error: %s", err.Error())
contactResponse.Status = "error"
contactResponse.Message = "Invalid request, please review your input and try again."
}