Merge branch 'master' into master
This commit is contained in:
		
						commit
						b6375edc98
					
				
							
								
								
									
										59
									
								
								README.md
								
								
								
								
							
							
						
						
									
										59
									
								
								README.md
								
								
								
								
							|  | @ -36,33 +36,38 @@ docker-compose up | ||||||
| 
 | 
 | ||||||
| ## Environment Variables | ## Environment Variables | ||||||
| 
 | 
 | ||||||
| | Variable                    | Description                                                                                                                                                  | Default                            | | | 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                                | | | `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                         | | | `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`            | 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`                            | | | `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_PASSWORD`             | The password for the user on the login page. Will be hashed automatically. Used for db initialization only                                                   | `admin`                            | | | `WGUI_USERNAME`             | The username for the login page. 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_PASSWORD`             | The password for the user on the login page. Will be hashed automatically. Used for db initialization only                                                                  | `admin`                            | | ||||||
| | `WGUI_ENDPOINT_ADDRESS`     | The default endpoint address used in global settings where clients should connect to                                                                         | Resolved to your public ip address | | | `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_FAVICON_FILE_PATH`    | The file path used as website favicon                                                                                                                        | Embedded WireGuard logo            | | | `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_DNS`                  | The default DNS servers (comma-separated-list) used in the global settings                                                                                   | `1.1.1.1`                          | | | `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_MTU`                  | The default MTU used in global settings                                                                                                                      | `1450`                             | | | `WGUI_ENDPOINT_ADDRESS`     | The default endpoint address used in global settings where clients should connect to                                                                                        | Resolved to your public ip address | | ||||||
| | `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings                                                                                            | `15`                               | | | `WGUI_FAVICON_FILE_PATH`    | The file path used as website favicon                                                                                                                                       | Embedded WireGuard logo            | | ||||||
| | `WGUI_FIREWALL_MARK`        | The default WireGuard firewall mark                                                                                                                          | `0xca6c`  (51820)                  | | | `WGUI_DNS`                  | The default DNS servers (comma-separated-list) used in the global settings                                                                                                  | `1.1.1.1`                          | | ||||||
| | `WGUI_TABLE`                | The default WireGuard table value settings                                                                                                                   | `auto`                             | | | `WGUI_MTU`                  | The default MTU used in global settings                                                                                                                                     | `1450`                             | | ||||||
| | `WGUI_CONFIG_FILE_PATH`     | The default WireGuard config file path used in global settings                                                                                               | `/etc/wireguard/wg0.conf`          | | | `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings                                                                                                           | `15`                               | | ||||||
| | `WGUI_LOG_LEVEL`            | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF`                                                                              | `INFO`                             | | | `WGUI_FIREWALL_MARK`        | The default WireGuard firewall mark                                                                                                                                         | `0xca6c`  (51820)                  | | ||||||
| | `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                                | | | `WGUI_TABLE`                | The default WireGuard table value settings                                                                                                                                  | `auto`                             | | ||||||
| | `EMAIL_FROM_ADDRESS`        | The sender email address                                                                                                                                     | N/A                                | | | `WGUI_CONFIG_FILE_PATH`     | The default WireGuard config file path used in global settings                                                                                                              | `/etc/wireguard/wg0.conf`          | | ||||||
| | `EMAIL_FROM_NAME`           | The sender name                                                                                                                                              | `WireGuard UI`                     | | | `WGUI_LOG_LEVEL`            | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF`                                                                                             | `INFO`                             | | ||||||
| | `SENDGRID_API_KEY`          | The SendGrid api key                                                                                                                                         | N/A                                | | | `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                                | | ||||||
| | `SMTP_HOSTNAME`             | The SMTP IP address or hostname                                                                                                                              | `127.0.0.1`                        | | | `EMAIL_FROM_ADDRESS`        | The sender email address                                                                                                                                                    | N/A                                | | ||||||
| | `SMTP_PORT`                 | The SMTP port                                                                                                                                                | `25`                               | | | `EMAIL_FROM_NAME`           | The sender name                                                                                                                                                             | `WireGuard UI`                     | | ||||||
| | `SMTP_USERNAME`             | The SMTP username                                                                                                                                            | N/A                                | | | `SENDGRID_API_KEY`          | The SendGrid api key                                                                                                                                                        | N/A                                | | ||||||
| | `SMTP_PASSWORD`             | The SMTP user password                                                                                                                                       | N/A                                | | | `SENDGRID_API_KEY_FILE`     | Optional filepath for the SendGrid api key. Leave `SENDGRID_API_KEY` blank to take effect                                                                                   | N/A                                | | ||||||
| | `SMTP_AUTH_TYPE`            | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE`                                                                                      | `NONE`                             | | | `SMTP_HOSTNAME`             | The SMTP IP address or hostname                                                                                                                                             | `127.0.0.1`                        | | ||||||
| | `SMTP_ENCRYPTION`           | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS`                                                                           | `STARTTLS`                         | | | `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 | ### Defaults for server configuration | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"regexp" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -26,6 +27,8 @@ import ( | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/util" | 	"github.com/ngoduykhanh/wireguard-ui/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$") | ||||||
|  | 
 | ||||||
| // Health check handler
 | // Health check handler
 | ||||||
| func Health() echo.HandlerFunc { | func Health() echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
|  | @ -63,6 +66,10 @@ func Login(db store.IStore) echo.HandlerFunc { | ||||||
| 		password := data["password"].(string) | 		password := data["password"].(string) | ||||||
| 		rememberMe := data["rememberMe"].(bool) | 		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) | 		dbuser, err := db.GetUserByName(username) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"}) | 			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
 | // GetUser handler returns a JSON object of single user
 | ||||||
| func GetUser(db store.IStore) echo.HandlerFunc { | func GetUser(db store.IStore) echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
| 
 |  | ||||||
| 		username := c.Param("username") | 		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)) { | 		if !isAdmin(c) && (username != currentUser(c)) { | ||||||
| 			return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"}) | 			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 | 			admin = false | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if !usernameRegexp.MatchString(previousUsername) { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		user, err := db.GetUserByName(previousUsername) | 		user, err := db.GetUserByName(previousUsername) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) | 			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"}) | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | ||||||
| 		} else { | 		} else { | ||||||
| 			user.Username = username | 			user.Username = username | ||||||
|  | @ -261,7 +275,7 @@ func CreateUser(db store.IStore) echo.HandlerFunc { | ||||||
| 		password := data["password"].(string) | 		password := data["password"].(string) | ||||||
| 		admin := data["admin"].(bool) | 		admin := data["admin"].(bool) | ||||||
| 
 | 
 | ||||||
| 		if username == "" { | 		if username == "" || !usernameRegexp.MatchString(username) { | ||||||
| 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | ||||||
| 		} else { | 		} else { | ||||||
| 			user.Username = username | 			user.Username = username | ||||||
|  | @ -303,6 +317,10 @@ func RemoveUser(db store.IStore) echo.HandlerFunc { | ||||||
| 
 | 
 | ||||||
| 		username := data["username"].(string) | 		username := data["username"].(string) | ||||||
| 
 | 
 | ||||||
|  | 		if !usernameRegexp.MatchString(username) { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if username == currentUser(c) { | 		if username == currentUser(c) { | ||||||
| 			return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"}) | 			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 { | 	return func(c echo.Context) error { | ||||||
| 
 | 
 | ||||||
| 		clientID := c.Param("id") | 		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{ | 		qrCodeSettings := model.QRCodeSettings{ | ||||||
| 			Enabled:       true, | 			Enabled:       true, | ||||||
| 			IncludeDNS:    true, | 			IncludeDNS:    true, | ||||||
|  | @ -485,6 +508,10 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon | ||||||
| 		c.Bind(&payload) | 		c.Bind(&payload) | ||||||
| 		// TODO validate email
 | 		// 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{ | 		qrCodeSettings := model.QRCodeSettings{ | ||||||
| 			Enabled:       true, | 			Enabled:       true, | ||||||
| 			IncludeDNS:    true, | 			IncludeDNS:    true, | ||||||
|  | @ -536,6 +563,10 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { | ||||||
| 		var _client model.Client | 		var _client model.Client | ||||||
| 		c.Bind(&_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
 | 		// validate client existence
 | ||||||
| 		clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false}) | 		clientData, err := db.GetClientByID(_client.ID, model.QRCodeSettings{Enabled: false}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -614,6 +645,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc { | ||||||
| 		client.AllocatedIPs = _client.AllocatedIPs | 		client.AllocatedIPs = _client.AllocatedIPs | ||||||
| 		client.AllowedIPs = _client.AllowedIPs | 		client.AllowedIPs = _client.AllowedIPs | ||||||
| 		client.ExtraAllowedIPs = _client.ExtraAllowedIPs | 		client.ExtraAllowedIPs = _client.ExtraAllowedIPs | ||||||
|  | 		client.Endpoint = _client.Endpoint | ||||||
| 		client.PublicKey = _client.PublicKey | 		client.PublicKey = _client.PublicKey | ||||||
| 		client.PresharedKey = _client.PresharedKey | 		client.PresharedKey = _client.PresharedKey | ||||||
| 		client.UpdatedAt = time.Now().UTC() | 		client.UpdatedAt = time.Now().UTC() | ||||||
|  | @ -642,6 +674,10 @@ func SetClientStatus(db store.IStore) echo.HandlerFunc { | ||||||
| 		clientID := data["id"].(string) | 		clientID := data["id"].(string) | ||||||
| 		status := data["status"].(bool) | 		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}) | 		clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) | 			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"}) | 			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}) | 		clientData, err := db.GetClientByID(clientID, model.QRCodeSettings{Enabled: false}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err) | 			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
 | 		// set response header for downloading
 | ||||||
| 		c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name)) | 		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) | 		client := new(model.Client) | ||||||
| 		c.Bind(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
 | 		// delete client from database
 | ||||||
| 
 | 
 | ||||||
| 		if err := db.DeleteClient(client.ID); err != nil { | 		if err := db.DeleteClient(client.ID); err != nil { | ||||||
|  |  | ||||||
							
								
								
									
										50
									
								
								main.go
								
								
								
								
							
							
						
						
									
										50
									
								
								main.go
								
								
								
								
							|  | @ -4,9 +4,6 @@ import ( | ||||||
| 	"embed" | 	"embed" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/labstack/echo/v4" |  | ||||||
| 	"github.com/labstack/gommon/log" |  | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" |  | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
|  | @ -15,6 +12,10 @@ import ( | ||||||
| 	"net" | 	"net" | ||||||
| 	"syscall" | 	"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/emailer" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/handler" | 	"github.com/ngoduykhanh/wireguard-ui/handler" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/router" | 	"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.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname") | ||||||
| 	flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port") | 	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(&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.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(&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(&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(&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(&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(&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") | 	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() | 	flag.Parse() | ||||||
| 
 | 
 | ||||||
| 	// update runtime config
 | 	// update runtime config
 | ||||||
|  | @ -149,15 +175,19 @@ func main() { | ||||||
| 
 | 
 | ||||||
| 	app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession) | 	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 { | 	if !util.DisableLogin { | ||||||
| 		app.GET(util.BasePath+"/login", handler.LoginPage()) | 		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+"/logout", handler.Logout(), handler.ValidSession) | ||||||
| 		app.GET(util.BasePath+"/profile", handler.LoadProfile(db), 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.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+"/update-user", handler.UpdateUser(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 		app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.NeedsAdmin) | 		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.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+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin) | ||||||
| 		app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) | 		app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ type Client struct { | ||||||
| 	AllocatedIPs    []string  `json:"allocated_ips"` | 	AllocatedIPs    []string  `json:"allocated_ips"` | ||||||
| 	AllowedIPs      []string  `json:"allowed_ips"` | 	AllowedIPs      []string  `json:"allowed_ips"` | ||||||
| 	ExtraAllowedIPs []string  `json:"extra_allowed_ips"` | 	ExtraAllowedIPs []string  `json:"extra_allowed_ips"` | ||||||
|  | 	Endpoint        string    `json:"endpoint"` | ||||||
| 	UseServerDNS    bool      `json:"use_server_dns"` | 	UseServerDNS    bool      `json:"use_server_dns"` | ||||||
| 	Enabled         bool      `json:"enabled"` | 	Enabled         bool      `json:"enabled"` | ||||||
| 	CreatedAt       time.Time `json:"created_at"` | 	CreatedAt       time.Time `json:"created_at"` | ||||||
|  |  | ||||||
|  | @ -23,5 +23,6 @@ type ServerInterface struct { | ||||||
| 	ListenPort int       `json:"listen_port,string"` // ,string to get listen_port string input as int
 | 	ListenPort int       `json:"listen_port,string"` // ,string to get listen_port string input as int
 | ||||||
| 	UpdatedAt  time.Time `json:"updated_at"` | 	UpdatedAt  time.Time `json:"updated_at"` | ||||||
| 	PostUp     string    `json:"post_up"` | 	PostUp     string    `json:"post_up"` | ||||||
|  | 	PreDown    string    `json:"pre_down"` | ||||||
| 	PostDown   string    `json:"post_down"` | 	PostDown   string    `json:"post_down"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package model | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"net" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  | @ -18,7 +19,13 @@ func (host WakeOnLanHost) ResolveResourceName() (string, error) { | ||||||
| 		return "", errors.New("mac Address is Empty") | 		return "", errors.New("mac Address is Empty") | ||||||
| 	} | 	} | ||||||
| 	resourceName = strings.ToUpper(resourceName) | 	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" | const WakeOnLanHostCollectionName = "wake_on_lan_hosts" | ||||||
|  |  | ||||||
|  | @ -38,12 +38,12 @@ func New(dbPath string) (*JsonDB, error) { | ||||||
| func (o *JsonDB) Init() error { | func (o *JsonDB) Init() error { | ||||||
| 	var clientPath string = path.Join(o.dbPath, "clients") | 	var clientPath string = path.Join(o.dbPath, "clients") | ||||||
| 	var serverPath string = path.Join(o.dbPath, "server") | 	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 wakeOnLanHostsPath string = path.Join(o.dbPath, "wake_on_lan_hosts") | ||||||
| 	var serverInterfacePath string = path.Join(serverPath, "interfaces.json") | 	var serverInterfacePath string = path.Join(serverPath, "interfaces.json") | ||||||
| 	var serverKeyPairPath string = path.Join(serverPath, "keypair.json") | 	var serverKeyPairPath string = path.Join(serverPath, "keypair.json") | ||||||
| 	var globalSettingPath string = path.Join(serverPath, "global_settings.json") | 	var globalSettingPath string = path.Join(serverPath, "global_settings.json") | ||||||
| 	var hashesPath string = path.Join(serverPath, "hashes.json") | 	var hashesPath string = path.Join(serverPath, "hashes.json") | ||||||
| 	var userPath string = path.Join(serverPath, "users.json") |  | ||||||
| 
 | 
 | ||||||
| 	// create directories if they do not exist
 | 	// create directories if they do not exist
 | ||||||
| 	if _, err := os.Stat(clientPath); os.IsNotExist(err) { | 	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) { | 	if _, err := os.Stat(serverPath); os.IsNotExist(err) { | ||||||
| 		os.MkdirAll(serverPath, os.ModePerm) | 		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) { | 	if _, err := os.Stat(userPath); os.IsNotExist(err) { | ||||||
| 		os.MkdirAll(userPath, os.ModePerm) | 		os.MkdirAll(userPath, os.ModePerm) | ||||||
| 	} | 	} | ||||||
|  | 	if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { | ||||||
|  | 		os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// server's interface
 | 	// server's interface
 | ||||||
| 	if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { | 	if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { | ||||||
|  | @ -68,7 +68,10 @@ func (o *JsonDB) Init() error { | ||||||
| 		serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") | 		serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") | ||||||
| 		serverInterface.UpdatedAt = time.Now().UTC() | 		serverInterface.UpdatedAt = time.Now().UTC() | ||||||
| 		o.conn.Write("server", "interfaces", serverInterface) | 		o.conn.Write("server", "interfaces", serverInterface) | ||||||
| 		os.Chmod(serverInterfacePath, 0600) | 		err := util.ManagePerms(serverInterfacePath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// server's key pair
 | 	// server's key pair
 | ||||||
|  | @ -83,7 +86,10 @@ func (o *JsonDB) Init() error { | ||||||
| 		serverKeyPair.PublicKey = key.PublicKey().String() | 		serverKeyPair.PublicKey = key.PublicKey().String() | ||||||
| 		serverKeyPair.UpdatedAt = time.Now().UTC() | 		serverKeyPair.UpdatedAt = time.Now().UTC() | ||||||
| 		o.conn.Write("server", "keypair", serverKeyPair) | 		o.conn.Write("server", "keypair", serverKeyPair) | ||||||
| 		os.Chmod(serverKeyPairPath, 0600) | 		err = util.ManagePerms(serverKeyPairPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// global settings
 | 	// global settings
 | ||||||
|  | @ -108,7 +114,10 @@ func (o *JsonDB) Init() error { | ||||||
| 		globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) | 		globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) | ||||||
| 		globalSetting.UpdatedAt = time.Now().UTC() | 		globalSetting.UpdatedAt = time.Now().UTC() | ||||||
| 		o.conn.Write("server", "global_settings", globalSetting) | 		o.conn.Write("server", "global_settings", globalSetting) | ||||||
| 		os.Chmod(globalSettingPath, 0600) | 		err := util.ManagePerms(globalSettingPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// hashes
 | 	// hashes
 | ||||||
|  | @ -117,7 +126,10 @@ func (o *JsonDB) Init() error { | ||||||
| 		clientServerHashes.Client = "none" | 		clientServerHashes.Client = "none" | ||||||
| 		clientServerHashes.Server = "none" | 		clientServerHashes.Server = "none" | ||||||
| 		o.conn.Write("server", "hashes", clientServerHashes) | 		o.conn.Write("server", "hashes", clientServerHashes) | ||||||
| 		os.Chmod(hashesPath, 0600) | 		err := util.ManagePerms(hashesPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// user info
 | 	// user info
 | ||||||
|  | @ -128,26 +140,30 @@ func (o *JsonDB) Init() error { | ||||||
| 		user.Admin = util.DefaultIsAdmin | 		user.Admin = util.DefaultIsAdmin | ||||||
| 		user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "") | 		user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "") | ||||||
| 		if user.PasswordHash == "" { | 		if user.PasswordHash == "" { | ||||||
| 			plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) | 			user.PasswordHash = util.LookupEnvOrFile(util.PasswordHashFileEnvVar, "") | ||||||
| 			hash, err := util.HashPassword(plaintext) | 			if user.PasswordHash == "" { | ||||||
| 			if err != nil { | 				plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) | ||||||
| 				return err | 				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) | 		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 | 	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
 | // GetUsers func to get all users from the database
 | ||||||
| func (o *JsonDB) GetUsers() ([]model.User, error) { | func (o *JsonDB) GetUsers() ([]model.User, error) { | ||||||
| 	var users []model.User | 	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 { | func (o *JsonDB) SaveUser(user model.User) error { | ||||||
| 	userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") | 	userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") | ||||||
| 	output := o.conn.Write("users", user.Username, user) | 	output := o.conn.Write("users", user.Username, user) | ||||||
| 	os.Chmod(userPath, 0600) | 	err := util.ManagePerms(userPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -295,7 +314,10 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti | ||||||
| func (o *JsonDB) SaveClient(client model.Client) error { | func (o *JsonDB) SaveClient(client model.Client) error { | ||||||
| 	clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") | 	clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") | ||||||
| 	output := o.conn.Write("clients", client.ID, client) | 	output := o.conn.Write("clients", client.ID, client) | ||||||
| 	os.Chmod(clientPath, 0600) | 	err := util.ManagePerms(clientPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -306,21 +328,30 @@ func (o *JsonDB) DeleteClient(clientID string) error { | ||||||
| func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { | func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { | ||||||
| 	serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") | 	serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") | ||||||
| 	output := o.conn.Write("server", "interfaces", serverInterface) | 	output := o.conn.Write("server", "interfaces", serverInterface) | ||||||
| 	os.Chmod(serverInterfacePath, 0600) | 	err := util.ManagePerms(serverInterfacePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { | func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { | ||||||
| 	serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") | 	serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") | ||||||
| 	output := o.conn.Write("server", "keypair", serverKeyPair) | 	output := o.conn.Write("server", "keypair", serverKeyPair) | ||||||
| 	os.Chmod(serverKeyPairPath, 0600) | 	err := util.ManagePerms(serverKeyPairPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { | func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { | ||||||
| 	globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") | 	globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") | ||||||
| 	output := o.conn.Write("server", "global_settings", globalSettings) | 	output := o.conn.Write("server", "global_settings", globalSettings) | ||||||
| 	os.Chmod(globalSettingsPath, 0600) | 	err := util.ManagePerms(globalSettingsPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -336,6 +367,9 @@ func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) { | ||||||
| func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { | func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { | ||||||
| 	hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") | 	hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") | ||||||
| 	output := o.conn.Write("server", "hashes", hashes) | 	output := o.conn.Write("server", "hashes", hashes) | ||||||
| 	os.Chmod(hashesPath, 0600) | 	err := util.ManagePerms(hashesPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ package jsondb | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" |  | ||||||
| 	"path" | 	"path" | ||||||
| 
 | 
 | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/model" | 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (o *JsonDB) GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) { | 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") | 	wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json") | ||||||
| 	output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) | 	output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) | ||||||
| 	os.Chmod(wakeOnLanHostPath, 0600) | 	err = util.ManagePerms(wakeOnLanHostPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return output | 	return output | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -232,6 +232,10 @@ | ||||||
|                                 </label> |                                 </label> | ||||||
|                                 <input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}"> |                                 <input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}"> | ||||||
|                             </div> |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="client_endpoint" class="control-label">Endpoint</label> | ||||||
|  |                                 <input type="text" class="form-control" id="client_endpoint" name="client_endpoint"> | ||||||
|  |                             </div> | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
|                                 <div class="icheck-primary d-inline"> |                                 <div class="icheck-primary d-inline"> | ||||||
|                                     <input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}> |                                     <input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}> | ||||||
|  | @ -413,6 +417,7 @@ | ||||||
|             const email = $("#client_email").val(); |             const email = $("#client_email").val(); | ||||||
|             const allocated_ips = $("#client_allocated_ips").val().split(","); |             const allocated_ips = $("#client_allocated_ips").val().split(","); | ||||||
|             const allowed_ips = $("#client_allowed_ips").val().split(","); |             const allowed_ips = $("#client_allowed_ips").val().split(","); | ||||||
|  |             const endpoint = $("#client_endpoint").val(); | ||||||
|             let use_server_dns = false; |             let use_server_dns = false; | ||||||
|             let extra_allowed_ips = []; |             let extra_allowed_ips = []; | ||||||
| 
 | 
 | ||||||
|  | @ -434,7 +439,7 @@ | ||||||
|             const preshared_key = $("#client_preshared_key").val(); |             const preshared_key = $("#client_preshared_key").val(); | ||||||
| 
 | 
 | ||||||
|             const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, |             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}; |                 "public_key": public_key, "preshared_key": preshared_key}; | ||||||
| 
 | 
 | ||||||
|             $.ajax({ |             $.ajax({ | ||||||
|  | @ -492,6 +497,7 @@ | ||||||
|             'defaultText': 'Add More', |             'defaultText': 'Add More', | ||||||
|             'removeWithBackspace': true, |             'removeWithBackspace': true, | ||||||
|             'minChars': 0, |             'minChars': 0, | ||||||
|  |             'minInputWidth': '100%', | ||||||
|             'placeholderColor': '#666666' |             'placeholderColor': '#666666' | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -503,6 +509,7 @@ | ||||||
|             'defaultText': 'Add More', |             'defaultText': 'Add More', | ||||||
|             'removeWithBackspace': true, |             'removeWithBackspace': true, | ||||||
|             'minChars': 0, |             'minChars': 0, | ||||||
|  |             'minInputWidth': '100%', | ||||||
|             'placeholderColor': '#666666' |             'placeholderColor': '#666666' | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -513,6 +520,7 @@ | ||||||
|             'defaultText': 'Add More', |             'defaultText': 'Add More', | ||||||
|             'removeWithBackspace': true, |             'removeWithBackspace': true, | ||||||
|             'minChars': 0, |             'minChars': 0, | ||||||
|  |             'minInputWidth': '100%', | ||||||
|             'placeholderColor': '#666666' |             'placeholderColor': '#666666' | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -113,6 +113,10 @@ Wireguard Clients | ||||||
|                         <input type="text" data-role="tagsinput" class="form-control" |                         <input type="text" data-role="tagsinput" class="form-control" | ||||||
|                                id="_client_extra_allowed_ips"> |                                id="_client_extra_allowed_ips"> | ||||||
|                     </div> |                     </div> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <label for="_client_endpoint" class="control-label">Endpoint</label> | ||||||
|  |                         <input type="text" class="form-control" id="_client_endpoint" name="client_endpoint"> | ||||||
|  |                     </div> | ||||||
|                     <div class="form-group"> |                     <div class="form-group"> | ||||||
|                         <div class="icheck-primary d-inline"> |                         <div class="icheck-primary d-inline"> | ||||||
|                             <input type="checkbox" id="_use_server_dns"> |                             <input type="checkbox" id="_use_server_dns"> | ||||||
|  | @ -423,6 +427,7 @@ Wireguard Clients | ||||||
|                     'defaultText': 'Add More', |                     'defaultText': 'Add More', | ||||||
|                     'removeWithBackspace': true, |                     'removeWithBackspace': true, | ||||||
|                     'minChars': 0, |                     'minChars': 0, | ||||||
|  |                     'minInputWidth': '100%', | ||||||
|                     'placeholderColor': '#666666' |                     'placeholderColor': '#666666' | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  | @ -434,6 +439,7 @@ Wireguard Clients | ||||||
|                     'defaultText': 'Add More', |                     'defaultText': 'Add More', | ||||||
|                     'removeWithBackspace': true, |                     'removeWithBackspace': true, | ||||||
|                     'minChars': 0, |                     'minChars': 0, | ||||||
|  |                     'minInputWidth': '100%', | ||||||
|                     'placeholderColor': '#666666' |                     'placeholderColor': '#666666' | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  | @ -444,6 +450,7 @@ Wireguard Clients | ||||||
|                     'defaultText': 'Add More', |                     'defaultText': 'Add More', | ||||||
|                     'removeWithBackspace' : true, |                     'removeWithBackspace' : true, | ||||||
|                     'minChars': 0, |                     'minChars': 0, | ||||||
|  |                     'minInputWidth': '100%', | ||||||
|                     'placeholderColor': '#666666' |                     'placeholderColor': '#666666' | ||||||
|                 }) |                 }) | ||||||
| 
 | 
 | ||||||
|  | @ -477,6 +484,8 @@ Wireguard Clients | ||||||
|                             modal.find("#_client_extra_allowed_ips").addTag(obj); |                             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("#_use_server_dns").prop("checked", client.use_server_dns); | ||||||
|                         modal.find("#_enabled").prop("checked", client.enabled); |                         modal.find("#_enabled").prop("checked", client.enabled); | ||||||
| 
 | 
 | ||||||
|  | @ -564,6 +573,8 @@ Wireguard Clients | ||||||
|                 extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); |                 extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             const endpoint = $("#_client_endpoint").val(); | ||||||
|  | 
 | ||||||
|             if ($("#_use_server_dns").is(':checked')){ |             if ($("#_use_server_dns").is(':checked')){ | ||||||
|                 use_server_dns = true; |                 use_server_dns = true; | ||||||
|             } |             } | ||||||
|  | @ -575,7 +586,8 @@ Wireguard Clients | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, |             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({ |             $.ajax({ | ||||||
|                 cache: false, |                 cache: false, | ||||||
|  |  | ||||||
|  | @ -203,6 +203,7 @@ Global Settings | ||||||
|             'defaultText': 'Add More', |             'defaultText': 'Add More', | ||||||
|             'removeWithBackspace': true, |             'removeWithBackspace': true, | ||||||
|             'minChars': 0, |             'minChars': 0, | ||||||
|  |             'minInputWidth': '100%', | ||||||
|             'placeholderColor': '#666666' |             'placeholderColor': '#666666' | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -42,6 +42,12 @@ Wireguard Server Settings | ||||||
|                                 <input type="text" class="form-control" id="post_up" name="post_up" |                                 <input type="text" class="form-control" id="post_up" name="post_up" | ||||||
|                                        placeholder="Post Up Script" value="{{ .serverInterface.PostUp }}"> |                                        placeholder="Post Up Script" value="{{ .serverInterface.PostUp }}"> | ||||||
|                             </div> |                             </div> | ||||||
|  |                             <div class="form-group"> | ||||||
|  |                                 <label for="pre_down">Pre Down Script</label> | ||||||
|  |                                 <input type="text" class="form-control" id="pre_down" name="pre_down" | ||||||
|  |                                        placeholder="Pre Down Script" value="{{ .serverInterface.PreDown }}"> | ||||||
|  |                             </div> | ||||||
|  | 
 | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
|                                 <label for="post_down">Post Down Script</label> |                                 <label for="post_down">Post Down Script</label> | ||||||
|                                 <input type="text" class="form-control" id="post_down" name="post_down" |                                 <input type="text" class="form-control" id="post_down" name="post_down" | ||||||
|  | @ -130,8 +136,9 @@ Wireguard Server Settings | ||||||
|             const addresses = $("#addresses").val().split(","); |             const addresses = $("#addresses").val().split(","); | ||||||
|             const listen_port = $("#listen_port").val(); |             const listen_port = $("#listen_port").val(); | ||||||
|             const post_up = $("#post_up").val(); |             const post_up = $("#post_up").val(); | ||||||
|  |             const pre_down = $("#pre_down").val(); | ||||||
|             const post_down = $("#post_down").val(); |             const post_down = $("#post_down").val(); | ||||||
|             const data = {"addresses": addresses, "listen_port": listen_port, "post_up": post_up, "post_down": post_down}; |             const data = {"addresses": addresses, "listen_port": listen_port, "post_up": post_up, "pre_down": pre_down, "post_down": post_down}; | ||||||
| 
 | 
 | ||||||
|             $.ajax({ |             $.ajax({ | ||||||
|                 cache: false, |                 cache: false, | ||||||
|  | @ -160,6 +167,7 @@ Wireguard Server Settings | ||||||
|             'defaultText': 'Add More', |             'defaultText': 'Add More', | ||||||
|             'removeWithBackspace': true, |             'removeWithBackspace': true, | ||||||
|             'minChars': 0, |             'minChars': 0, | ||||||
|  |             'minInputWidth': '100%', | ||||||
|             'placeholderColor': '#666666' |             'placeholderColor': '#666666' | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ ListenPort = {{ .serverConfig.Interface.ListenPort }} | ||||||
| PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }} | PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }} | ||||||
| {{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}} | {{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}} | ||||||
| PostUp = {{ .serverConfig.Interface.PostUp }} | PostUp = {{ .serverConfig.Interface.PostUp }} | ||||||
|  | PreDown = {{ .serverConfig.Interface.PreDown }} | ||||||
| PostDown = {{ .serverConfig.Interface.PostDown }} | PostDown = {{ .serverConfig.Interface.PostDown }} | ||||||
| Table = {{ .globalSettings.Table }} | Table = {{ .globalSettings.Table }} | ||||||
| 
 | 
 | ||||||
|  | @ -20,6 +21,7 @@ Table = {{ .globalSettings.Table }} | ||||||
| # Update at:    {{ .Client.UpdatedAt }} | # Update at:    {{ .Client.UpdatedAt }} | ||||||
| [Peer] | [Peer] | ||||||
| PublicKey = {{ .Client.PublicKey }} | PublicKey = {{ .Client.PublicKey }} | ||||||
| {{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }} | {{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}{{end}} | ||||||
| {{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}} | AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}{{end}} | ||||||
| {{end}}{{end}} | {{if .Client.Endpoint }}Endpoint = {{ .Client.Endpoint }}{{end}} | ||||||
|  | {{end}} | ||||||
|  |  | ||||||
|  | @ -30,12 +30,14 @@ const ( | ||||||
| 	DefaultDNS                             = "1.1.1.1" | 	DefaultDNS                             = "1.1.1.1" | ||||||
| 	DefaultMTU                             = 1450 | 	DefaultMTU                             = 1450 | ||||||
| 	DefaultPersistentKeepalive             = 15 | 	DefaultPersistentKeepalive             = 15 | ||||||
| 	DefaultFirewallMark                    = "0xca6c"  // i.e. 51820
 | 	DefaultFirewallMark                    = "0xca6c" // i.e. 51820
 | ||||||
| 	DefaultTable                           = "auto" | 	DefaultTable                           = "auto" | ||||||
| 	DefaultConfigFilePath                  = "/etc/wireguard/wg0.conf" | 	DefaultConfigFilePath                  = "/etc/wireguard/wg0.conf" | ||||||
| 	UsernameEnvVar                         = "WGUI_USERNAME" | 	UsernameEnvVar                         = "WGUI_USERNAME" | ||||||
| 	PasswordEnvVar                         = "WGUI_PASSWORD" | 	PasswordEnvVar                         = "WGUI_PASSWORD" | ||||||
|  | 	PasswordFileEnvVar                     = "WGUI_PASSWORD_FILE" | ||||||
| 	PasswordHashEnvVar                     = "WGUI_PASSWORD_HASH" | 	PasswordHashEnvVar                     = "WGUI_PASSWORD_HASH" | ||||||
|  | 	PasswordHashFileEnvVar                 = "WGUI_PASSWORD_HASH_FILE" | ||||||
| 	FaviconFilePathEnvVar                  = "WGUI_FAVICON_FILE_PATH" | 	FaviconFilePathEnvVar                  = "WGUI_FAVICON_FILE_PATH" | ||||||
| 	EndpointAddressEnvVar                  = "WGUI_ENDPOINT_ADDRESS" | 	EndpointAddressEnvVar                  = "WGUI_ENDPOINT_ADDRESS" | ||||||
| 	DNSEnvVar                              = "WGUI_DNS" | 	DNSEnvVar                              = "WGUI_DNS" | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								util/util.go
								
								
								
								
							
							
						
						
									
										25
									
								
								util/util.go
								
								
								
								
							|  | @ -1,11 +1,10 @@ | ||||||
| package util | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" |  | ||||||
| 	"golang.org/x/mod/sumdb/dirhash" |  | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/fs" | 	"io/fs" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | @ -19,6 +18,9 @@ import ( | ||||||
| 	"text/template" | 	"text/template" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||||
|  | 	"golang.org/x/mod/sumdb/dirhash" | ||||||
|  | 
 | ||||||
| 	externalip "github.com/glendc/go-external-ip" | 	externalip "github.com/glendc/go-external-ip" | ||||||
| 	"github.com/labstack/gommon/log" | 	"github.com/labstack/gommon/log" | ||||||
| 	"github.com/ngoduykhanh/wireguard-ui/model" | 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||||
|  | @ -466,6 +468,20 @@ func LookupEnvOrStrings(key string, defaultVal []string) []string { | ||||||
| 	return defaultVal | 	return defaultVal | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func LookupEnvOrFile(key string, defaultVal string) string { | ||||||
|  | 	if val, ok := os.LookupEnv(key); ok { | ||||||
|  | 		if file, err := os.Open(val); err == nil { | ||||||
|  | 			var content string | ||||||
|  | 			scanner := bufio.NewScanner(file) | ||||||
|  | 			for scanner.Scan() { | ||||||
|  | 				content += scanner.Text() | ||||||
|  | 			} | ||||||
|  | 			return content | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return defaultVal | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func StringFromEmbedFile(embed fs.FS, filename string) (string, error) { | func StringFromEmbedFile(embed fs.FS, filename string) (string, error) { | ||||||
| 	file, err := embed.Open(filename) | 	file, err := embed.Open(filename) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -540,3 +556,8 @@ func RandomString(length int) string { | ||||||
| 	} | 	} | ||||||
| 	return string(b) | 	return string(b) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func ManagePerms(path string) error { | ||||||
|  | 	err := os.Chmod(path, 0600) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue