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 @@

+