From c31636b66e1bfe34236e075278d22b06db69f749 Mon Sep 17 00:00:00 2001
From: Arminas
Date: Fri, 30 Dec 2022 19:10:27 +0200
Subject: [PATCH 1/4] Initial email settings UI commit
Added email settings page, settings now save in database, ability to send an email to client when it's created
---
emailer/smtp.go | 6 +-
handler/routes.go | 74 ++++++-
main.go | 52 +++--
model/setting.go | 15 ++
router/router.go | 6 +
store/jsondb/jsondb.go | 32 +++
store/store.go | 2 +
templates/base.html | 42 ++++
templates/email_settings.html | 364 ++++++++++++++++++++++++++++++++++
9 files changed, 568 insertions(+), 25 deletions(-)
create mode 100644 templates/email_settings.html
diff --git a/emailer/smtp.go b/emailer/smtp.go
index d1fdbae..dc2cac5 100644
--- a/emailer/smtp.go
+++ b/emailer/smtp.go
@@ -20,7 +20,7 @@ type SmtpMail struct {
from string
}
-func authType(authType string) mail.AuthType {
+func AuthType(authType string) mail.AuthType {
switch strings.ToUpper(authType) {
case "PLAIN":
return mail.AuthPlain
@@ -31,7 +31,7 @@ func authType(authType string) mail.AuthType {
}
}
-func encryptionType(encryptionType string) mail.Encryption {
+func EncryptionType(encryptionType string) mail.Encryption {
switch strings.ToUpper(encryptionType) {
case "SSL":
return mail.EncryptionSSL
@@ -45,7 +45,7 @@ func encryptionType(encryptionType string) mail.Encryption {
}
func NewSmtpMail(hostname string, port int, username string, password string, noTLSCheck bool, auth string, fromName, from string, encryption string) *SmtpMail {
- ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth), encryption: encryptionType(encryption)}
+ ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: AuthType(auth), encryption: EncryptionType(encryption)}
return &ans
}
diff --git a/handler/routes.go b/handler/routes.go
index 89dc341..403bda3 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -466,7 +466,7 @@ func NewClient(db store.IStore) echo.HandlerFunc {
}
// EmailClient handler to send the configuration via email
-func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailContent string) echo.HandlerFunc {
+func EmailClient(db store.IStore) echo.HandlerFunc {
type clientIdEmailPayload struct {
ID string `json:"id"`
Email string `json:"email"`
@@ -489,6 +489,15 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
}
+ // init mailer
+ var mailer emailer.Emailer
+ emailSetting, _ := db.GetEmailSettings()
+ if emailSetting.SendgridApiKey != "" {
+ mailer = emailer.NewSendgridApiMail(emailSetting.SendgridApiKey, emailSetting.EmailFromName, emailSetting.EmailFrom)
+ } else {
+ mailer = emailer.NewSmtpMail(emailSetting.SmtpHostname, emailSetting.SmtpPort, emailSetting.SmtpUsername, emailSetting.SmtpPassword, emailSetting.SmtpNoTLSCheck, emailSetting.SmtpAuthType, emailSetting.EmailFromName, emailSetting.EmailFrom, emailSetting.SmtpEncryption)
+ }
+
// build config
server, _ := db.GetServer()
globalSettings, _ := db.GetGlobalSettings()
@@ -509,10 +518,11 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon
err = mailer.Send(
clientData.Client.Name,
payload.Email,
- emailSubject,
- emailContent,
+ emailSetting.DefaultEmailSubject,
+ emailSetting.DefaultEmailContent,
attachments,
)
+ fmt.Println("\n\n\n %s \n\n\n", err)
if err != nil {
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
@@ -748,6 +758,64 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc {
}
}
+// EmailSettings handler
+func EmailSettings(db store.IStore) echo.HandlerFunc {
+ return func(c echo.Context) error {
+
+ emailSettings, err := db.GetEmailSettings()
+ if err != nil {
+ log.Error("Cannot get email settings: ", err)
+ }
+
+ return c.Render(http.StatusOK, "email_settings.html", map[string]interface{}{
+ "baseData": model.BaseData{Active: "email-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)},
+ "emailSettings": emailSettings,
+ })
+ }
+}
+
+// EmailSettingsSubmit handler to update the email settings
+func EmailSettingsSubmit(db store.IStore) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ data := make(map[string]interface{})
+ err := json.NewDecoder(c.Request().Body).Decode(&data)
+
+ if err != nil {
+ return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
+ }
+
+ emailSetting, err := db.GetEmailSettings()
+ if err != nil {
+ log.Error("Cannot get email settings: ", err)
+ }
+
+ if len(data) == 2 {
+ emailSetting.DefaultEmailSubject = data["default_email_subject"].(string)
+ emailSetting.DefaultEmailContent = data["default_email_content"].(string)
+ } else {
+ emailSetting.SendgridApiKey = data["sendgrid_api_key"].(string)
+ emailSetting.EmailFromName = data["email_from_name"].(string)
+ emailSetting.EmailFrom = data["email_from"].(string)
+ emailSetting.SmtpHostname = data["smtp_hostname"].(string)
+ emailSetting.SmtpPort = int(data["smtp_port"].(float64))
+ emailSetting.SmtpUsername = data["smtp_username"].(string)
+ emailSetting.SmtpPassword = data["smtp_password"].(string)
+ emailSetting.SmtpNoTLSCheck = data["smtp_no_tls_check"].(bool)
+ emailSetting.SmtpAuthType = data["smtp_auth_type"].(string)
+ emailSetting.SmtpEncryption = data["smtp_encryption"].(string)
+ }
+
+ // write config to the database
+ if err := db.SaveEmailSettings(emailSetting); err != nil {
+ return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot update email settings"})
+ }
+
+ log.Infof("Updated email settings: %v", emailSetting)
+
+ return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated email settings successfully"})
+ }
+}
+
// Status handler
func Status(db store.IStore) echo.HandlerFunc {
type PeerVM struct {
diff --git a/main.go b/main.go
index aefc0bb..e522a80 100644
--- a/main.go
+++ b/main.go
@@ -11,7 +11,6 @@ import (
"time"
rice "github.com/GeertJohan/go.rice"
- "github.com/ngoduykhanh/wireguard-ui/emailer"
"github.com/ngoduykhanh/wireguard-ui/handler"
"github.com/ngoduykhanh/wireguard-ui/router"
"github.com/ngoduykhanh/wireguard-ui/store/jsondb"
@@ -42,18 +41,11 @@ var (
flagBasePath string
)
-const (
- defaultEmailSubject = "Your wireguard configuration"
- defaultEmailContent = `Hi,
-In this email you can find your personal configuration for our wireguard server.
-
-Best
-`
-)
-
func init() {
// command-line flags and env variables
+ // TODO move ENV_VAR variables and default values to config.go
+
flag.BoolVar(&flagDisableLogin, "disable-login", util.LookupEnvOrBool("DISABLE_LOGIN", flagDisableLogin), "Disable authentication on the app. This is potentially dangerous.")
flag.StringVar(&flagBindAddress, "bind-address", util.LookupEnvOrString("BIND_ADDRESS", flagBindAddress), "Address:Port to which the app will be bound.")
flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname")
@@ -127,6 +119,34 @@ func main() {
// create the wireguard config on start, if it doesn't exist
initServerConfig(db, tmplBox)
+ // check and update email settings if necessary
+ if util.SendgridApiKey != "" {
+ emailSetting, _ := db.GetEmailSettings()
+ emailSetting.SendgridApiKey = util.SendgridApiKey
+ emailSetting.EmailFromName = util.EmailFromName
+ emailSetting.EmailFrom = util.EmailFrom
+ err := db.SaveEmailSettings(emailSetting)
+ if err != nil {
+ panic(err)
+ }
+ }
+ if util.SmtpUsername != "" {
+ emailSetting, _ := db.GetEmailSettings()
+ emailSetting.EmailFromName = util.EmailFromName
+ emailSetting.EmailFrom = util.EmailFrom
+ emailSetting.SmtpHostname = util.SmtpHostname
+ emailSetting.SmtpPort = util.SmtpPort
+ emailSetting.SmtpUsername = util.SmtpUsername
+ emailSetting.SmtpPassword = util.SmtpPassword
+ emailSetting.SmtpNoTLSCheck = util.SmtpNoTLSCheck
+ emailSetting.SmtpAuthType = util.SmtpAuthType
+ emailSetting.SmtpEncryption = util.SmtpEncryption
+ err := db.SaveEmailSettings(emailSetting)
+ if err != nil {
+ panic(err)
+ }
+ }
+
// register routes
app := router.New(tmplBox, extraData, util.SessionSecret)
@@ -145,17 +165,10 @@ func main() {
app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession)
}
- var sendmail emailer.Emailer
- if util.SendgridApiKey != "" {
- sendmail = emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
- } else {
- sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption)
- }
-
app.GET(util.BasePath+"/_health", handler.Health())
app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson)
- app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson)
+ app.POST(util.BasePath+"/email-client", handler.EmailClient(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson)
app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession)
@@ -163,7 +176,8 @@ func main() {
app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.NeedsAdmin)
-
+ app.GET(util.BasePath+"/email-settings", handler.EmailSettings(db), handler.ValidSession, handler.NeedsAdmin)
+ app.POST(util.BasePath+"/email-settings", handler.EmailSettingsSubmit(db), handler.ValidSession, handler.NeedsAdmin)
app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession)
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
diff --git a/model/setting.go b/model/setting.go
index e871591..8e2d35f 100644
--- a/model/setting.go
+++ b/model/setting.go
@@ -14,3 +14,18 @@ type GlobalSetting struct {
ConfigFilePath string `json:"config_file_path"`
UpdatedAt time.Time `json:"updated_at"`
}
+
+type EmailSetting struct {
+ SendgridApiKey string `json:"sendgrid_api_key"`
+ EmailFromName string `json:"email_from_name"`
+ EmailFrom string `json:"email_from"`
+ SmtpHostname string `json:"smtp_hostname"`
+ SmtpPort int `json:"smtp_port"`
+ SmtpUsername string `json:"smtp_username"`
+ SmtpPassword string `json:"smtp_password"`
+ SmtpNoTLSCheck bool `json:"smtp_no_tls_check"`
+ SmtpAuthType string `json:"smtp_auth_type"`
+ SmtpEncryption string `json:"smtp_encryption"`
+ DefaultEmailSubject string `json:"default_email_subject"`
+ DefaultEmailContent string `json:"default_email_content"`
+}
diff --git a/router/router.go b/router/router.go
index f262243..99e1b31 100644
--- a/router/router.go
+++ b/router/router.go
@@ -83,6 +83,11 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec
log.Fatal(err)
}
+ tmplEmailSettingsString, err := tmplBox.String("email_settings.html")
+ if err != nil {
+ log.Fatal(err)
+ }
+
tmplUsersSettingsString, err := tmplBox.String("users_settings.html")
if err != nil {
log.Fatal(err)
@@ -108,6 +113,7 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec
templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString))
templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString))
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
+ templates["email_settings.html"] = template.Must(template.New("email_settings").Funcs(funcs).Parse(tmplBaseString + tmplEmailSettingsString))
templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString))
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString))
diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go
index e6ebfb2..e68719e 100644
--- a/store/jsondb/jsondb.go
+++ b/store/jsondb/jsondb.go
@@ -42,6 +42,7 @@ func (o *JsonDB) Init() error {
var serverInterfacePath string = path.Join(serverPath, "interfaces.json")
var serverKeyPairPath string = path.Join(serverPath, "keypair.json")
var globalSettingPath string = path.Join(serverPath, "global_settings.json")
+ var emailSettingPath string = path.Join(serverPath, "email_settings.json")
var userPath string = path.Join(o.dbPath, "users")
// create directories if they do not exist
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
@@ -105,6 +106,27 @@ func (o *JsonDB) Init() error {
o.conn.Write("server", "global_settings", globalSetting)
}
+ // email settings
+ // TODO move ENV_VAR variables and default values to config.go
+ if _, err := os.Stat(emailSettingPath); os.IsNotExist(err) {
+ emailSetting := new(model.EmailSetting)
+ emailSetting.SendgridApiKey = util.LookupEnvOrString("SENDGRID_API_KEY", "")
+ emailSetting.EmailFromName = util.LookupEnvOrString("EMAIL_FROM_NAME", "WireGuard UI")
+ emailSetting.EmailFrom = util.LookupEnvOrString("EMAIL_FROM_ADDRESS", "")
+ emailSetting.SmtpHostname = util.LookupEnvOrString("SMTP_HOSTNAME", "127.0.0.1")
+ emailSetting.SmtpPort = util.LookupEnvOrInt("SMTP_PORT", 25)
+ emailSetting.SmtpUsername = util.LookupEnvOrString("SMTP_USERNAME", "")
+ emailSetting.SmtpPassword = util.LookupEnvOrString("SMTP_PASSWORD", "")
+ emailSetting.SmtpNoTLSCheck = util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", false)
+ emailSetting.SmtpAuthType = util.LookupEnvOrString("SMTP_AUTH_TYPE", "NONE")
+ emailSetting.SmtpEncryption = util.LookupEnvOrString("SMTP_ENCRYPTION", "STARTTLS")
+ emailSetting.DefaultEmailSubject = "Your wireguard configuration"
+ emailSetting.DefaultEmailContent = `Hi,
+In this email you can find your personal configuration for our wireguard server.
+Best
`
+ o.conn.Write("server", "email_settings", emailSetting)
+ }
+
// user info
results, err := o.conn.ReadAll("users")
if err != nil || len(results) < 1 {
@@ -295,3 +317,13 @@ func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error {
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
return o.conn.Write("server", "global_settings", globalSettings)
}
+
+// GetEmailSettings func to query email settings from the database
+func (o *JsonDB) GetEmailSettings() (model.EmailSetting, error) {
+ settings := model.EmailSetting{}
+ return settings, o.conn.Read("server", "email_settings", &settings)
+}
+
+func (o *JsonDB) SaveEmailSettings(emailSettings model.EmailSetting) error {
+ return o.conn.Write("server", "email_settings", emailSettings)
+}
diff --git a/store/store.go b/store/store.go
index 166bc3d..d59026d 100644
--- a/store/store.go
+++ b/store/store.go
@@ -11,6 +11,7 @@ type IStore interface {
SaveUser(user model.User) error
DeleteUser(username string) error
GetGlobalSettings() (model.GlobalSetting, error)
+ GetEmailSettings() (model.EmailSetting, error)
GetServer() (model.Server, error)
GetClients(hasQRCode bool) ([]model.ClientData, error)
GetClientByID(clientID string, qrCode model.QRCodeSettings) (model.ClientData, error)
@@ -19,6 +20,7 @@ type IStore interface {
SaveServerInterface(serverInterface model.ServerInterface) error
SaveServerKeyPair(serverKeyPair model.ServerKeypair) error
SaveGlobalSettings(globalSettings model.GlobalSetting) error
+ SaveEmailSettings(emailSettings model.EmailSetting) error
GetWakeOnLanHosts() ([]model.WakeOnLanHost, error)
GetWakeOnLanHost(macAddress string) (*model.WakeOnLanHost, error)
DeleteWakeOnHostLanHost(macAddress string) error
diff --git a/templates/base.html b/templates/base.html
index 227e35d..76fa0a7 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -134,6 +134,14 @@
+
+
+
+
+ Email Settings
+
+
+
@@ -226,6 +234,14 @@
+
Public and Preshared Keys
+
+{{end}}
From e9b628cd3fa5df070b52e4d289464e9436ec15c0 Mon Sep 17 00:00:00 2001
From: Arminas
Date: Fri, 30 Dec 2022 20:26:23 +0200
Subject: [PATCH 2/4] Minor fixes
Minor fixes
---
emailer/smtp.go | 6 +++---
handler/routes.go | 1 -
templates/email_settings.html | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/emailer/smtp.go b/emailer/smtp.go
index dc2cac5..d1fdbae 100644
--- a/emailer/smtp.go
+++ b/emailer/smtp.go
@@ -20,7 +20,7 @@ type SmtpMail struct {
from string
}
-func AuthType(authType string) mail.AuthType {
+func authType(authType string) mail.AuthType {
switch strings.ToUpper(authType) {
case "PLAIN":
return mail.AuthPlain
@@ -31,7 +31,7 @@ func AuthType(authType string) mail.AuthType {
}
}
-func EncryptionType(encryptionType string) mail.Encryption {
+func encryptionType(encryptionType string) mail.Encryption {
switch strings.ToUpper(encryptionType) {
case "SSL":
return mail.EncryptionSSL
@@ -45,7 +45,7 @@ func EncryptionType(encryptionType string) mail.Encryption {
}
func NewSmtpMail(hostname string, port int, username string, password string, noTLSCheck bool, auth string, fromName, from string, encryption string) *SmtpMail {
- ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: AuthType(auth), encryption: EncryptionType(encryption)}
+ ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth), encryption: encryptionType(encryption)}
return &ans
}
diff --git a/handler/routes.go b/handler/routes.go
index 403bda3..92c0191 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -522,7 +522,6 @@ func EmailClient(db store.IStore) echo.HandlerFunc {
emailSetting.DefaultEmailContent,
attachments,
)
- fmt.Println("\n\n\n %s \n\n\n", err)
if err != nil {
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
diff --git a/templates/email_settings.html b/templates/email_settings.html
index 4af81d6..5fecb59 100644
--- a/templates/email_settings.html
+++ b/templates/email_settings.html
@@ -70,7 +70,7 @@ Email Settings
-
From 09a26b3a02691da7d0c4179c25968d690fbdd4e6 Mon Sep 17 00:00:00 2001
From: Arminas
Date: Fri, 30 Dec 2022 21:09:01 +0200
Subject: [PATCH 3/4] Qr code filename fix for downloading
When downloading QR code, the filename will be Client name, instead of generic.
Ability to left click on QR code to download it.
---
templates/clients.html | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/templates/clients.html b/templates/clients.html
index 689c261..c513f2b 100644
--- a/templates/clients.html
+++ b/templates/clients.html
@@ -70,7 +70,9 @@ Wireguard Clients
-
![QR code]()
+
+
+