diff --git a/README.md b/README.md
index dec0978..b3f213c 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,9 @@ wireguard interface stats. See the `cap_add` and `network_mode` options on the d
Set the `SESSION_SECRET` environment variable to a random value.
-In order to sent the wireguard configuration to clients via email (using sendgrid api) set the following environment variables
+In order to sent the wireguard configuration to clients via email, set the following environment variables:
+
+- using SendGrid API
```
SENDGRID_API_KEY: Your sendgrid api key
@@ -41,6 +43,18 @@ EMAIL_FROM_ADDRESS: the email address you registered on sendgrid
EMAIL_FROM_NAME: the sender's email address
```
+- using SMTP
+
+```
+SMTP_HOSTNAME
+SMTP_PORT
+SMTP_USERNAME
+SMTP_PASSWORD
+SMTP_AUTH_TYPE
+EMAIL_FROM_ADDRESS: the sender's email address
+EMAIL_FROM_NAME: the sender's name
+```
+
### Using binary file
Download the binary file from the release and run it with command:
diff --git a/custom/js/helper.js b/custom/js/helper.js
index 6ce7f60..9dff4ee 100644
--- a/custom/js/helper.js
+++ b/custom/js/helper.js
@@ -24,11 +24,15 @@ function renderClientList(data) {
+
+ Scan
+
", name, address)
+}
+
+func (o *SmtpMail) Send(toName string, to string, subject string, content string, attachments []Attachment) error {
+ server := mail.NewSMTPClient()
+
+ server.Host = o.hostname
+ server.Port = o.port
+ server.Authentication = o.authType
+ server.Username = o.username
+ server.Password = o.password
+ server.Encryption = mail.EncryptionSTARTTLS
+ server.KeepAlive = false
+ server.ConnectTimeout = 10 * time.Second
+ server.SendTimeout = 10 * time.Second
+
+ if o.noTLSCheck {
+ server.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+ }
+
+ smtpClient, err := server.Connect()
+
+ if err != nil {
+ return err
+ }
+
+ email := mail.NewMSG()
+ email.SetFrom("From "+addressField(o.from, o.fromName)).
+ AddTo(addressField(to, toName)).
+ SetSubject(subject).
+ SetBody(mail.TextHTML, content)
+
+ for _, v := range attachments {
+ email.Attach(&mail.File{Name: v.Name, Data: v.Data})
+ }
+
+ err = email.Send(smtpClient)
+
+ return err
+}
diff --git a/go.mod b/go.mod
index fb535fe..918d704 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/sendgrid/rest v2.6.4+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.10.0+incompatible
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
+ github.com/xhit/go-simple-mail/v2 v2.10.0
golang.zx2c4.com/wireguard v0.0.20200121 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
diff --git a/go.sum b/go.sum
index b31de99..206090f 100644
--- a/go.sum
+++ b/go.sum
@@ -147,6 +147,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
+github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/handler/routes.go b/handler/routes.go
index 156e701..9c18fc4 100644
--- a/handler/routes.go
+++ b/handler/routes.go
@@ -160,6 +160,12 @@ func NewClient(db store.IStore) echo.HandlerFunc {
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"})
}
+ // validate extra AllowedIPs
+ if util.ValidateExtraAllowedIPs(client.ExtraAllowedIPs) == false {
+ log.Warnf("Invalid Extra AllowedIPs input from user: %v", client.ExtraAllowedIPs)
+ return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra AllowedIPs must be in CIDR format"})
+ }
+
// gen ID
guid := xid.New()
client.ID = guid.String()
@@ -274,6 +280,11 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"})
}
+ if util.ValidateExtraAllowedIPs(_client.ExtraAllowedIPs) == false {
+ log.Warnf("Invalid Allowed IPs input from user: %v", _client.ExtraAllowedIPs)
+ return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra Allowed IPs must be in CIDR format"})
+ }
+
// map new data
client.Name = _client.Name
client.Email = _client.Email
@@ -281,6 +292,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
client.UseServerDNS = _client.UseServerDNS
client.AllocatedIPs = _client.AllocatedIPs
client.AllowedIPs = _client.AllowedIPs
+ client.ExtraAllowedIPs = _client.ExtraAllowedIPs
client.UpdatedAt = time.Now().UTC()
// write to the database
@@ -628,7 +640,7 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr),
})
}
- if (strings.Contains(ip, ":")) {
+ if strings.Contains(ip, ":") {
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip))
} else {
suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip))
diff --git a/main.go b/main.go
index 8cf5dc9..99e0460 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,12 @@ var (
// configuration variables
flagDisableLogin bool = false
flagBindAddress string = "0.0.0.0:5000"
+ flagSmtpHostname string = "127.0.0.1"
+ flagSmtpPort int = 25
+ flagSmtpUsername string
+ flagSmtpPassword string
+ flagSmtpAuthType string = "None"
+ flagSmtpNoTLSCheck bool = false
flagSendgridApiKey string
flagEmailFrom string
flagEmailFromName string = "WireGuard UI"
@@ -45,6 +51,12 @@ func init() {
// command-line flags and env variables
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")
+ flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port")
+ flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Password")
+ flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password")
+ flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.")
+ flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : Plain or None.")
flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey), "Your sendgrid api key.")
flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.")
flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
@@ -54,6 +66,12 @@ func init() {
// update runtime config
util.DisableLogin = flagDisableLogin
util.BindAddress = flagBindAddress
+ util.SmtpHostname = flagSmtpHostname
+ util.SmtpPort = flagSmtpPort
+ util.SmtpUsername = flagSmtpUsername
+ util.SmtpPassword = flagSmtpPassword
+ util.SmtpAuthType = flagSmtpAuthType
+ util.SmtpNoTLSCheck = flagSmtpNoTLSCheck
util.SendgridApiKey = flagSendgridApiKey
util.EmailFrom = flagEmailFrom
util.EmailFromName = flagEmailFromName
@@ -103,7 +121,12 @@ func main() {
app.POST("/login", handler.Login(db))
}
- sendmail := emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
+ 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)
+ }
app.GET("/_health", handler.Health())
app.GET("/logout", handler.Logout(), handler.ValidSession)
diff --git a/model/client.go b/model/client.go
index 8e6ba52..c1c7487 100644
--- a/model/client.go
+++ b/model/client.go
@@ -6,18 +6,19 @@ import (
// Client model
type Client struct {
- ID string `json:"id"`
- PrivateKey string `json:"private_key"`
- PublicKey string `json:"public_key"`
- PresharedKey string `json:"preshared_key"`
- Name string `json:"name"`
- Email string `json:"email"`
- AllocatedIPs []string `json:"allocated_ips"`
- AllowedIPs []string `json:"allowed_ips"`
- UseServerDNS bool `json:"use_server_dns"`
- Enabled bool `json:"enabled"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID string `json:"id"`
+ PrivateKey string `json:"private_key"`
+ PublicKey string `json:"public_key"`
+ PresharedKey string `json:"preshared_key"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ AllocatedIPs []string `json:"allocated_ips"`
+ AllowedIPs []string `json:"allowed_ips"`
+ ExtraAllowedIPs []string `json:"extra_allowed_ips"`
+ UseServerDNS bool `json:"use_server_dns"`
+ Enabled bool `json:"enabled"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
}
// ClientData includes the Client and extra data
diff --git a/templates/base.html b/templates/base.html
index 6705c6d..b3c254c 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -163,6 +163,11 @@
+
+ Extra Allowed IPs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -86,6 +104,11 @@ Wireguard Clients
Allowed IPs
+
+ Extra Allowed IPs
+
+
@@ -275,7 +298,7 @@ Wireguard Clients
// Edit client modal event
$(document).ready(function () {
- $("#modal_edit_client").on('shown.bs.modal', function (event) {
+ $("#modal_edit_client").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const client_id = button.data('clientid');
@@ -302,6 +325,16 @@ Wireguard Clients
'placeholderColor': '#666666'
});
+ modal.find("#_client_extra_allowed_ips").tagsInput({
+ 'width': '100%',
+ 'height': '75%',
+ 'interactive': true,
+ 'defaultText': 'Add More',
+ 'removeWithBackspace' : true,
+ 'minChars': 0,
+ 'placeholderColor': '#666666'
+ })
+
// update client modal data
$.ajax({
cache: false,
@@ -327,6 +360,11 @@ Wireguard Clients
modal.find("#_client_allowed_ips").addTag(obj);
});
+ modal.find("#_client_extra_allowed_ips").importTags('');
+ client.extra_allowed_ips.forEach(function (obj) {
+ modal.find("#_client_extra_allowed_ips").addTag(obj);
+ });
+
modal.find("#_use_server_dns").prop("checked", client.use_server_dns);
modal.find("#_enabled").prop("checked", client.enabled);
},
@@ -371,6 +409,11 @@ Wireguard Clients
const allocated_ips = $("#_client_allocated_ips").val().split(",");
const allowed_ips = $("#_client_allowed_ips").val().split(",");
let use_server_dns = false;
+ let extra_allowed_ips = [];
+
+ if( $("#_client_extra_allowed_ips").val() !== "" ) {
+ extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(",");
+ }
if ($("#_use_server_dns").is(':checked')){
use_server_dns = true;
@@ -383,7 +426,7 @@ Wireguard Clients
}
const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips,
- "allowed_ips": allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled};
+ "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled};
$.ajax({
cache: false,
@@ -415,7 +458,7 @@ Wireguard Clients
}
}
- $("#modal_email_client").on('shown.bs.modal', function (event) {
+ $("#modal_email_client").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const client_id = button.data('clientid');
@@ -439,6 +482,31 @@ Wireguard Clients
});
});
+ $("#modal_qr_client").on('show.bs.modal', function (event) {
+ let modal = $(this);
+ const button = $(event.relatedTarget);
+ const client_id = button.data('clientid');
+ const QRCodeImg = modal.find("#qr_code");
+ QRCodeImg.hide();
+ $.ajax({
+ cache: false,
+ method: 'GET',
+ url: '/api/client/' + client_id,
+ dataType: 'json',
+ contentType: "application/json",
+ success: function (resp) {
+ const client = resp.Client;
+
+ modal.find(".modal-title").text("QR Code for " + client.name);
+ QRCodeImg.attr('src', resp.QRCode).show();
+ },
+ error: function (jqXHR, exception) {
+ const responseJson = jQuery.parseJSON(jqXHR.responseText);
+ toastr.error(responseJson['message']);
+ }
+ });
+ });
+
$(document).ready(function () {
$.validator.setDefaults({
submitHandler: function (form) {
diff --git a/templates/wg.conf b/templates/wg.conf
index baca924..745a92f 100644
--- a/templates/wg.conf
+++ b/templates/wg.conf
@@ -20,5 +20,5 @@ PostDown = {{ .serverConfig.Interface.PostDown }}
[Peer]
PublicKey = {{ .Client.PublicKey }}
{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}
-{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}
+{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}
{{end}}{{end}}
diff --git a/util/config.go b/util/config.go
index acdb720..80cbc9c 100644
--- a/util/config.go
+++ b/util/config.go
@@ -4,6 +4,12 @@ package util
var (
DisableLogin bool
BindAddress string
+ SmtpHostname string
+ SmtpPort int
+ SmtpUsername string
+ SmtpPassword string
+ SmtpNoTLSCheck bool
+ SmtpAuthType string
SendgridApiKey string
EmailFrom string
EmailFromName string
diff --git a/util/util.go b/util/util.go
index b923f47..7c347a9 100644
--- a/util/util.go
+++ b/util/util.go
@@ -80,10 +80,18 @@ func ValidateCIDR(cidr string) bool {
}
// ValidateCIDRList to validate a list of network CIDR
-func ValidateCIDRList(cidrs []string) bool {
+func ValidateCIDRList(cidrs []string, allowEmpty bool) bool {
for _, cidr := range cidrs {
- if ValidateCIDR(cidr) == false {
- return false
+ if allowEmpty {
+ if len(cidr) > 0 {
+ if ValidateCIDR(cidr) == false {
+ return false
+ }
+ }
+ } else {
+ if ValidateCIDR(cidr) == false {
+ return false
+ }
}
}
return true
@@ -91,7 +99,15 @@ func ValidateCIDRList(cidrs []string) bool {
// ValidateAllowedIPs to validate allowed ip addresses in CIDR format
func ValidateAllowedIPs(cidrs []string) bool {
- if ValidateCIDRList(cidrs) == false {
+ if ValidateCIDRList(cidrs, false) == false {
+ return false
+ }
+ return true
+}
+
+// ValidateExtraAllowedIPs to validate extra Allowed ip addresses, allowing empty strings
+func ValidateExtraAllowedIPs(cidrs []string) bool {
+ if ValidateCIDRList(cidrs, true) == false {
return false
}
return true
@@ -99,7 +115,7 @@ func ValidateAllowedIPs(cidrs []string) bool {
// ValidateServerAddresses to validate allowed ip addresses in CIDR format
func ValidateServerAddresses(cidrs []string) bool {
- if ValidateCIDRList(cidrs) == false {
+ if ValidateCIDRList(cidrs, false) == false {
return false
}
return true