diff --git a/README.md b/README.md index afefcf3..d585868 100644 --- a/README.md +++ b/README.md @@ -36,33 +36,38 @@ docker-compose up ## Environment Variables -| Variable | Description | Default | -|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| -| `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard)) | N/A | -| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 | -| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | -| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | -| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` | -| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A | -| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | Resolved to your public ip address | -| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo | -| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` | -| `WGUI_MTU` | The default MTU used in global settings | `1450` | -| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` | -| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) | -| `WGUI_TABLE` | The default WireGuard table value settings | `auto` | -| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` | -| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` | -| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A | -| `EMAIL_FROM_ADDRESS` | The sender email address | N/A | -| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` | -| `SENDGRID_API_KEY` | The SendGrid api key | N/A | -| `SMTP_HOSTNAME` | The SMTP IP address or hostname | `127.0.0.1` | -| `SMTP_PORT` | The SMTP port | `25` | -| `SMTP_USERNAME` | The SMTP username | N/A | -| `SMTP_PASSWORD` | The SMTP user password | N/A | -| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` | -| `SMTP_ENCRYPTION` | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` | +| Variable | Description | Default | +|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------| +| `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard) | N/A | +| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 | +| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A | +| `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A | +| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` | +| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` | +| `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A | +| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A | +| `WGUI_PASSWORD_HASH_FILE` | Optional filepath for the user login password hash. (alternative to `WGUI_PASSWORD_FILE`). Used for db initialization only. Leave `WGUI_PASSWORD_HASH` blank to take effect | N/A | +| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | Resolved to your public ip address | +| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo | +| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` | +| `WGUI_MTU` | The default MTU used in global settings | `1450` | +| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` | +| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) | +| `WGUI_TABLE` | The default WireGuard table value settings | `auto` | +| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` | +| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` | +| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A | +| `EMAIL_FROM_ADDRESS` | The sender email address | N/A | +| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` | +| `SENDGRID_API_KEY` | The SendGrid api key | N/A | +| `SENDGRID_API_KEY_FILE` | Optional filepath for the SendGrid api key. Leave `SENDGRID_API_KEY` blank to take effect | N/A | +| `SMTP_HOSTNAME` | The SMTP IP address or hostname | `127.0.0.1` | +| `SMTP_PORT` | The SMTP port | `25` | +| `SMTP_USERNAME` | The SMTP username | N/A | +| `SMTP_PASSWORD` | The SMTP user password | N/A | +| `SMTP_PASSWORD_FILE` | Optional filepath for the SMTP user password. Leave `SMTP_PASSWORD` blank to take effect | N/A | +| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` | +| `SMTP_ENCRYPTION` | The encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` | ### Defaults for server configuration diff --git a/handler/routes.go b/handler/routes.go index a2ceeaf..aa5461b 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -8,6 +8,7 @@ import ( "io/fs" "net/http" "os" + "regexp" "sort" "strings" "time" @@ -26,6 +27,8 @@ import ( "github.com/ngoduykhanh/wireguard-ui/util" ) +var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$") + // Health check handler func Health() echo.HandlerFunc { return func(c echo.Context) error { @@ -63,6 +66,10 @@ func Login(db store.IStore) echo.HandlerFunc { password := data["password"].(string) rememberMe := data["rememberMe"].(bool) + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + dbuser, err := db.GetUserByName(username) if err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"}) @@ -135,9 +142,12 @@ func GetUsers(db store.IStore) echo.HandlerFunc { // GetUser handler returns a JSON object of single user func GetUser(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - username := c.Param("username") + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + if !isAdmin(c) && (username != currentUser(c)) { return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"}) } @@ -200,12 +210,16 @@ func UpdateUser(db store.IStore) echo.HandlerFunc { admin = false } + if !usernameRegexp.MatchString(previousUsername) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + user, err := db.GetUserByName(previousUsername) if err != nil { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) } - if username == "" { + if username == "" || !usernameRegexp.MatchString(username) { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) } else { user.Username = username @@ -261,7 +275,7 @@ func CreateUser(db store.IStore) echo.HandlerFunc { password := data["password"].(string) admin := data["admin"].(bool) - if username == "" { + if username == "" || !usernameRegexp.MatchString(username) { return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) } else { user.Username = username @@ -303,6 +317,10 @@ func RemoveUser(db store.IStore) echo.HandlerFunc { username := data["username"].(string) + if !usernameRegexp.MatchString(username) { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } + if username == currentUser(c) { return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"}) } @@ -357,6 +375,11 @@ func GetClient(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { clientID := c.Param("id") + + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + qrCodeSettings := model.QRCodeSettings{ Enabled: true, IncludeDNS: true, @@ -485,6 +508,10 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon c.Bind(&payload) // TODO validate email + if _, err := xid.FromString(payload.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + qrCodeSettings := model.QRCodeSettings{ Enabled: true, IncludeDNS: true, @@ -536,6 +563,10 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { var _client model.Client c.Bind(&_client) + if _, err := xid.FromString(_client.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + // validate client existence clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false}) if err != nil { @@ -614,6 +645,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { client.AllocatedIPs = _client.AllocatedIPs client.AllowedIPs = _client.AllowedIPs client.ExtraAllowedIPs = _client.ExtraAllowedIPs + client.Endpoint = _client.Endpoint client.PublicKey = _client.PublicKey client.PresharedKey = _client.PresharedKey client.UpdatedAt = time.Now().UTC() @@ -642,6 +674,10 @@ func SetClientStatus(db store.IStore) echo.HandlerFunc { clientID := data["id"].(string) status := data["status"].(bool) + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) if err != nil { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) @@ -667,6 +703,10 @@ func DownloadClient(db store.IStore) echo.HandlerFunc { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"}) } + if _, err := xid.FromString(clientID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) if err != nil { log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err) @@ -689,7 +729,7 @@ func DownloadClient(db store.IStore) echo.HandlerFunc { // set response header for downloading c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name)) - return c.Stream(http.StatusOK, "text/plain", reader) + return c.Stream(http.StatusOK, "text/conf", reader) } } @@ -700,6 +740,10 @@ func RemoveClient(db store.IStore) echo.HandlerFunc { client := new(model.Client) c.Bind(client) + if _, err := xid.FromString(client.ID); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid client ID"}) + } + // delete client from database if err := db.DeleteClient(client.ID); err != nil { diff --git a/main.go b/main.go index 74fcb0f..fd4bc90 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,6 @@ import ( "embed" "flag" "fmt" - "github.com/labstack/echo/v4" - "github.com/labstack/gommon/log" - "github.com/ngoduykhanh/wireguard-ui/store" "io/fs" "net/http" "os" @@ -15,6 +12,10 @@ import ( "net" "syscall" + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" + "github.com/ngoduykhanh/wireguard-ui/store" + "github.com/ngoduykhanh/wireguard-ui/emailer" "github.com/ngoduykhanh/wireguard-ui/handler" "github.com/ngoduykhanh/wireguard-ui/router" @@ -73,16 +74,41 @@ func init() { 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 Username") - 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(&flagSmtpEncryption, "smtp-encryption", util.LookupEnvOrString("SMTP_ENCRYPTION", flagSmtpEncryption), "SMTP Encryption : NONE, SSL, SSLTLS, TLS or STARTTLS (by default)") flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN 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.") - flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret), "The key used to encrypt session cookies.") flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") + + var ( + smtpPasswordLookup = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword) + sengridApiKeyLookup = util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey) + sessionSecretLookup = util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret) + ) + + // check empty smtpPassword env var + if smtpPasswordLookup != "" { + flag.StringVar(&flagSmtpPassword, "smtp-password", smtpPasswordLookup, "SMTP Password") + } else { + flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrFile("SMTP_PASSWORD_FILE", flagSmtpPassword), "SMTP Password File") + } + + // check empty sengridApiKey env var + if sengridApiKeyLookup != "" { + flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", sengridApiKeyLookup, "Your sendgrid api key.") + } else { + flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrFile("SENDGRID_API_KEY_FILE", flagSendgridApiKey), "File containing your sendgrid api key.") + } + + // check empty sessionSecret env var + if sessionSecretLookup != "" { + flag.StringVar(&flagSessionSecret, "session-secret", sessionSecretLookup, "The key used to encrypt session cookies.") + } else { + flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrFile("SESSION_SECRET_FILE", flagSessionSecret), "File containing the key used to encrypt session cookies.") + } + flag.Parse() // update runtime config @@ -149,15 +175,19 @@ func main() { app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession) + // Important: Make sure that all non-GET routes check the request content type using handler.ContentTypeJson to + // mitigate CSRF attacks. This is effective, because browsers don't allow setting the Content-Type header on + // cross-origin requests. + if !util.DisableLogin { app.GET(util.BasePath+"/login", handler.LoginPage()) - app.POST(util.BasePath+"/login", handler.Login(db)) + app.POST(util.BasePath+"/login", handler.Login(db), handler.ContentTypeJson) app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession) app.GET(util.BasePath+"/profile", handler.LoadProfile(db), handler.ValidSession) app.GET(util.BasePath+"/users-settings", handler.UsersSettings(db), handler.ValidSession, handler.NeedsAdmin) - app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession) - app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.NeedsAdmin) - app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.NeedsAdmin) + app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession, handler.ContentTypeJson) + app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin) app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) } diff --git a/model/client.go b/model/client.go index be5fa76..95342f0 100644 --- a/model/client.go +++ b/model/client.go @@ -15,6 +15,7 @@ type Client struct { AllocatedIPs []string `json:"allocated_ips"` AllowedIPs []string `json:"allowed_ips"` ExtraAllowedIPs []string `json:"extra_allowed_ips"` + Endpoint string `json:"endpoint"` UseServerDNS bool `json:"use_server_dns"` Enabled bool `json:"enabled"` CreatedAt time.Time `json:"created_at"` diff --git a/model/server.go b/model/server.go index 0784eea..0aa804f 100644 --- a/model/server.go +++ b/model/server.go @@ -23,5 +23,6 @@ type ServerInterface struct { ListenPort int `json:"listen_port,string"` // ,string to get listen_port string input as int UpdatedAt time.Time `json:"updated_at"` PostUp string `json:"post_up"` + PreDown string `json:"pre_down"` PostDown string `json:"post_down"` } diff --git a/model/wake_on_lan_host.go b/model/wake_on_lan_host.go index 2d1ab73..73966f0 100644 --- a/model/wake_on_lan_host.go +++ b/model/wake_on_lan_host.go @@ -2,6 +2,7 @@ package model import ( "errors" + "net" "strings" "time" ) @@ -18,7 +19,13 @@ func (host WakeOnLanHost) ResolveResourceName() (string, error) { return "", errors.New("mac Address is Empty") } resourceName = strings.ToUpper(resourceName) - return strings.ReplaceAll(resourceName, ":", "-"), nil + resourceName = strings.ReplaceAll(resourceName, ":", "-") + + if _, err := net.ParseMAC(resourceName); err != nil { + return "", errors.New("invalid mac address") + } + + return resourceName, nil } const WakeOnLanHostCollectionName = "wake_on_lan_hosts" diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index 0b674b0..757ccdc 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -38,12 +38,12 @@ func New(dbPath string) (*JsonDB, error) { func (o *JsonDB) Init() error { var clientPath string = path.Join(o.dbPath, "clients") var serverPath string = path.Join(o.dbPath, "server") + var userPath string = path.Join(o.dbPath, "users") var wakeOnLanHostsPath string = path.Join(o.dbPath, "wake_on_lan_hosts") 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 hashesPath string = path.Join(serverPath, "hashes.json") - var userPath string = path.Join(serverPath, "users.json") // create directories if they do not exist if _, err := os.Stat(clientPath); os.IsNotExist(err) { @@ -52,12 +52,12 @@ func (o *JsonDB) Init() error { if _, err := os.Stat(serverPath); os.IsNotExist(err) { os.MkdirAll(serverPath, os.ModePerm) } - if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { - os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) - } if _, err := os.Stat(userPath); os.IsNotExist(err) { os.MkdirAll(userPath, os.ModePerm) } + if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { + os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) + } // server's interface if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { @@ -68,7 +68,10 @@ func (o *JsonDB) Init() error { serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") serverInterface.UpdatedAt = time.Now().UTC() o.conn.Write("server", "interfaces", serverInterface) - os.Chmod(serverInterfacePath, 0600) + err := util.ManagePerms(serverInterfacePath) + if err != nil { + return err + } } // server's key pair @@ -83,7 +86,10 @@ func (o *JsonDB) Init() error { serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() o.conn.Write("server", "keypair", serverKeyPair) - os.Chmod(serverKeyPairPath, 0600) + err = util.ManagePerms(serverKeyPairPath) + if err != nil { + return err + } } // global settings @@ -108,7 +114,10 @@ func (o *JsonDB) Init() error { globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) globalSetting.UpdatedAt = time.Now().UTC() o.conn.Write("server", "global_settings", globalSetting) - os.Chmod(globalSettingPath, 0600) + err := util.ManagePerms(globalSettingPath) + if err != nil { + return err + } } // hashes @@ -117,7 +126,10 @@ func (o *JsonDB) Init() error { clientServerHashes.Client = "none" clientServerHashes.Server = "none" o.conn.Write("server", "hashes", clientServerHashes) - os.Chmod(hashesPath, 0600) + err := util.ManagePerms(hashesPath) + if err != nil { + return err + } } // user info @@ -128,26 +140,30 @@ func (o *JsonDB) Init() error { user.Admin = util.DefaultIsAdmin user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "") if user.PasswordHash == "" { - plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) - hash, err := util.HashPassword(plaintext) - if err != nil { - return err + user.PasswordHash = util.LookupEnvOrFile(util.PasswordHashFileEnvVar, "") + if user.PasswordHash == "" { + plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) + if plaintext == util.DefaultPassword { + plaintext = util.LookupEnvOrFile(util.PasswordFileEnvVar, util.DefaultPassword) + } + hash, err := util.HashPassword(plaintext) + if err != nil { + return err + } + user.PasswordHash = hash } - user.PasswordHash = hash } + o.conn.Write("users", user.Username, user) - os.Chmod(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"), 0600) + err = util.ManagePerms(path.Join(path.Join(o.dbPath, "users"), user.Username+".json")) + if err != nil { + return err + } } return nil } -// GetUser func to query user info from the database -func (o *JsonDB) GetUser() (model.User, error) { - user := model.User{} - return user, o.conn.Read("server", "users", &user) -} - // GetUsers func to get all users from the database func (o *JsonDB) GetUsers() ([]model.User, error) { var users []model.User @@ -182,7 +198,10 @@ func (o *JsonDB) GetUserByName(username string) (model.User, error) { func (o *JsonDB) SaveUser(user model.User) error { userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") output := o.conn.Write("users", user.Username, user) - os.Chmod(userPath, 0600) + err := util.ManagePerms(userPath) + if err != nil { + return err + } return output } @@ -295,7 +314,10 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti func (o *JsonDB) SaveClient(client model.Client) error { clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") output := o.conn.Write("clients", client.ID, client) - os.Chmod(clientPath, 0600) + err := util.ManagePerms(clientPath) + if err != nil { + return err + } return output } @@ -306,21 +328,30 @@ func (o *JsonDB) DeleteClient(clientID string) error { func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") output := o.conn.Write("server", "interfaces", serverInterface) - os.Chmod(serverInterfacePath, 0600) + err := util.ManagePerms(serverInterfacePath) + if err != nil { + return err + } return output } func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") output := o.conn.Write("server", "keypair", serverKeyPair) - os.Chmod(serverKeyPairPath, 0600) + err := util.ManagePerms(serverKeyPairPath) + if err != nil { + return err + } return output } func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") output := o.conn.Write("server", "global_settings", globalSettings) - os.Chmod(globalSettingsPath, 0600) + err := util.ManagePerms(globalSettingsPath) + if err != nil { + return err + } return output } @@ -336,6 +367,9 @@ func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) { func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") output := o.conn.Write("server", "hashes", hashes) - os.Chmod(hashesPath, 0600) + err := util.ManagePerms(hashesPath) + if err != nil { + return err + } return output } diff --git a/store/jsondb/jsondb_wake_on_lan.go b/store/jsondb/jsondb_wake_on_lan.go index a0ee935..661ba05 100644 --- a/store/jsondb/jsondb_wake_on_lan.go +++ b/store/jsondb/jsondb_wake_on_lan.go @@ -3,10 +3,10 @@ package jsondb import ( "encoding/json" "fmt" - "os" "path" "github.com/ngoduykhanh/wireguard-ui/model" + "github.com/ngoduykhanh/wireguard-ui/util" ) func (o *JsonDB) GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) { @@ -70,7 +70,11 @@ func (o *JsonDB) SaveWakeOnLanHost(host model.WakeOnLanHost) error { wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json") output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) - os.Chmod(wakeOnLanHostPath, 0600) + err = util.ManagePerms(wakeOnLanHostPath) + if err != nil { + return err + } + return output } diff --git a/templates/base.html b/templates/base.html index 0865ff3..c2fa367 100644 --- a/templates/base.html +++ b/templates/base.html @@ -232,6 +232,10 @@ +
+ + +
@@ -413,6 +417,7 @@ const email = $("#client_email").val(); const allocated_ips = $("#client_allocated_ips").val().split(","); const allowed_ips = $("#client_allowed_ips").val().split(","); + const endpoint = $("#client_endpoint").val(); let use_server_dns = false; let extra_allowed_ips = []; @@ -434,7 +439,7 @@ const preshared_key = $("#client_preshared_key").val(); const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, - "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, + "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ @@ -492,6 +497,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -503,6 +509,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -513,6 +520,7 @@ 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/clients.html b/templates/clients.html index bcd5855..8b4e4ab 100644 --- a/templates/clients.html +++ b/templates/clients.html @@ -113,6 +113,10 @@ Wireguard Clients
+
+ + +
@@ -423,6 +427,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -434,6 +439,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); @@ -444,6 +450,7 @@ Wireguard Clients 'defaultText': 'Add More', 'removeWithBackspace' : true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }) @@ -477,6 +484,8 @@ Wireguard Clients modal.find("#_client_extra_allowed_ips").addTag(obj); }); + modal.find("#_client_endpoint").val(client.endpoint); + modal.find("#_use_server_dns").prop("checked", client.use_server_dns); modal.find("#_enabled").prop("checked", client.enabled); @@ -564,6 +573,8 @@ Wireguard Clients extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); } + const endpoint = $("#_client_endpoint").val(); + if ($("#_use_server_dns").is(':checked')){ use_server_dns = true; } @@ -575,7 +586,8 @@ Wireguard Clients } const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, - "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; + "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, + "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; $.ajax({ cache: false, diff --git a/templates/global_settings.html b/templates/global_settings.html index cafb630..73b3c93 100644 --- a/templates/global_settings.html +++ b/templates/global_settings.html @@ -203,6 +203,7 @@ Global Settings 'defaultText': 'Add More', 'removeWithBackspace': true, 'minChars': 0, + 'minInputWidth': '100%', 'placeholderColor': '#666666' }); diff --git a/templates/server.html b/templates/server.html index 366d301..e1116a6 100644 --- a/templates/server.html +++ b/templates/server.html @@ -42,6 +42,12 @@ Wireguard Server Settings
+
+ + +
+