User management panel (#289)
This commit is contained in:
		
							parent
							
								
									aadf099f50
								
							
						
					
					
						commit
						6dd5590940
					
				|  | @ -78,6 +78,34 @@ function renderClientList(data) { | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function renderUserList(data) { | ||||||
|  |     $.each(data, function(index, obj) { | ||||||
|  |         let clientStatusHtml = '>' | ||||||
|  | 
 | ||||||
|  |         // render user html content
 | ||||||
|  |         let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="user_${obj.username}">
 | ||||||
|  |                         <div class="info-box"> | ||||||
|  |                             <div class="info-box-content"> | ||||||
|  |                                 <div class="btn-group"> | ||||||
|  |                                      <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#modal_edit_user" data-username="${obj.username}">Edit</button> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div class="btn-group"> | ||||||
|  |                                     <button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" | ||||||
|  |                                         data-target="#modal_remove_user" data-username="${obj.username}">Delete</button> | ||||||
|  |                                 </div> | ||||||
|  |                                 <hr> | ||||||
|  |                                 <span class="info-box-text"><i class="fas fa-user"></i> ${obj.username}</span> | ||||||
|  |                                 <span class="info-box-text"><i class="fas fa-terminal"></i> ${obj.admin? 'Administrator':'Manager'}</span> | ||||||
|  |                                 </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div>` | ||||||
|  | 
 | ||||||
|  |         // add the user html elements to the list
 | ||||||
|  |         $('#users-list').append(html); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| function prettyDateTime(timeStr) { | function prettyDateTime(timeStr) { | ||||||
|     const dt = new Date(timeStr); |     const dt = new Date(timeStr); | ||||||
|     const offsetMs = dt.getTimezoneOffset() * 60 * 1000; |     const offsetMs = dt.getTimezoneOffset() * 60 * 1000; | ||||||
|  |  | ||||||
|  | @ -52,39 +52,54 @@ func LoginPage() echo.HandlerFunc { | ||||||
| // Login for signing in handler
 | // Login for signing in handler
 | ||||||
| func Login(db store.IStore) echo.HandlerFunc { | func Login(db store.IStore) echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
| 		user := new(model.User) | 		data := make(map[string]interface{}) | ||||||
| 		c.Bind(user) | 		err := json.NewDecoder(c.Request().Body).Decode(&data) | ||||||
| 
 | 
 | ||||||
| 		dbuser, err := db.GetUser() | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		username := data["username"].(string) | ||||||
|  | 		password := data["password"].(string) | ||||||
|  | 		rememberMe := data["rememberMe"].(bool) | ||||||
|  | 
 | ||||||
|  | 		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"}) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		userCorrect := subtle.ConstantTimeCompare([]byte(user.Username), []byte(dbuser.Username)) == 1 | 		userCorrect := subtle.ConstantTimeCompare([]byte(username), []byte(dbuser.Username)) == 1 | ||||||
| 
 | 
 | ||||||
| 		var passwordCorrect bool | 		var passwordCorrect bool | ||||||
| 		if dbuser.PasswordHash != "" { | 		if dbuser.PasswordHash != "" { | ||||||
| 			match, err := util.VerifyHash(dbuser.PasswordHash, user.Password) | 			match, err := util.VerifyHash(dbuser.PasswordHash, password) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify password"}) | 				return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify password"}) | ||||||
| 			} | 			} | ||||||
| 			passwordCorrect = match | 			passwordCorrect = match | ||||||
| 		} else { | 		} else { | ||||||
| 			passwordCorrect = subtle.ConstantTimeCompare([]byte(user.Password), []byte(dbuser.Password)) == 1 | 			passwordCorrect = subtle.ConstantTimeCompare([]byte(password), []byte(dbuser.Password)) == 1 | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if userCorrect && passwordCorrect { | 		if userCorrect && passwordCorrect { | ||||||
| 			// TODO: refresh the token
 | 			// TODO: refresh the token
 | ||||||
|  | 			ageMax := 0 | ||||||
|  | 			expiration := time.Now().Add(24 * time.Hour) | ||||||
|  | 			if rememberMe { | ||||||
|  | 				ageMax = 86400 | ||||||
|  | 				expiration.Add(144 * time.Hour) | ||||||
|  | 			} | ||||||
| 			sess, _ := session.Get("session", c) | 			sess, _ := session.Get("session", c) | ||||||
| 			sess.Options = &sessions.Options{ | 			sess.Options = &sessions.Options{ | ||||||
| 				Path:     util.BasePath, | 				Path:     util.BasePath, | ||||||
| 				MaxAge:   86400, | 				MaxAge:   ageMax, | ||||||
| 				HttpOnly: true, | 				HttpOnly: true, | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// set session_token
 | 			// set session_token
 | ||||||
| 			tokenUID := xid.New().String() | 			tokenUID := xid.New().String() | ||||||
| 			sess.Values["username"] = user.Username | 			sess.Values["username"] = dbuser.Username | ||||||
|  | 			sess.Values["admin"] = dbuser.Admin | ||||||
| 			sess.Values["session_token"] = tokenUID | 			sess.Values["session_token"] = tokenUID | ||||||
| 			sess.Save(c.Request(), c.Response()) | 			sess.Save(c.Request(), c.Response()) | ||||||
| 
 | 
 | ||||||
|  | @ -92,7 +107,7 @@ func Login(db store.IStore) echo.HandlerFunc { | ||||||
| 			cookie := new(http.Cookie) | 			cookie := new(http.Cookie) | ||||||
| 			cookie.Name = "session_token" | 			cookie.Name = "session_token" | ||||||
| 			cookie.Value = tokenUID | 			cookie.Value = tokenUID | ||||||
| 			cookie.Expires = time.Now().Add(24 * time.Hour) | 			cookie.Expires = expiration | ||||||
| 			c.SetCookie(cookie) | 			c.SetCookie(cookie) | ||||||
| 
 | 
 | ||||||
| 			return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Logged in successfully"}) | 			return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Logged in successfully"}) | ||||||
|  | @ -102,6 +117,40 @@ func Login(db store.IStore) echo.HandlerFunc { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetUsers handler return a JSON list of all users
 | ||||||
|  | func GetUsers(db store.IStore) echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 
 | ||||||
|  | 		usersList, err := db.GetUsers() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ | ||||||
|  | 				false, fmt.Sprintf("Cannot get user list: %v", err), | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, usersList) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 !isAdmin(c) && (username != currentUser(c)) { | ||||||
|  | 			return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		userData, err := db.GetUserByName(username) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "User not found"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, userData) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Logout to log a user out
 | // Logout to log a user out
 | ||||||
| func Logout() echo.HandlerFunc { | func Logout() echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
|  | @ -113,21 +162,23 @@ func Logout() echo.HandlerFunc { | ||||||
| // LoadProfile to load user information
 | // LoadProfile to load user information
 | ||||||
| func LoadProfile(db store.IStore) echo.HandlerFunc { | func LoadProfile(db store.IStore) echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
| 
 |  | ||||||
| 		userInfo, err := db.GetUser() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error("Cannot get user information: ", err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return c.Render(http.StatusOK, "profile.html", map[string]interface{}{ | 		return c.Render(http.StatusOK, "profile.html", map[string]interface{}{ | ||||||
| 			"baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c)}, | 			"baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"userInfo": userInfo, |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdateProfile to update user information
 | // UsersSettings handler
 | ||||||
| func UpdateProfile(db store.IStore) echo.HandlerFunc { | func UsersSettings(db store.IStore) echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		return c.Render(http.StatusOK, "users_settings.html", map[string]interface{}{ | ||||||
|  | 			"baseData": model.BaseData{Active: "users-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateUser to update user information
 | ||||||
|  | func UpdateUser(db store.IStore) echo.HandlerFunc { | ||||||
| 	return func(c echo.Context) error { | 	return func(c echo.Context) error { | ||||||
| 		data := make(map[string]interface{}) | 		data := make(map[string]interface{}) | ||||||
| 		err := json.NewDecoder(c.Request().Body).Decode(&data) | 		err := json.NewDecoder(c.Request().Body).Decode(&data) | ||||||
|  | @ -138,8 +189,18 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { | ||||||
| 
 | 
 | ||||||
| 		username := data["username"].(string) | 		username := data["username"].(string) | ||||||
| 		password := data["password"].(string) | 		password := data["password"].(string) | ||||||
|  | 		previousUsername := data["previous_username"].(string) | ||||||
|  | 		admin := data["admin"].(bool) | ||||||
| 
 | 
 | ||||||
| 		user, err := db.GetUser() | 		if !isAdmin(c) && (previousUsername != currentUser(c)) { | ||||||
|  | 			return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !isAdmin(c) { | ||||||
|  | 			admin = false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		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()}) | ||||||
| 		} | 		} | ||||||
|  | @ -150,6 +211,13 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { | ||||||
| 			user.Username = username | 			user.Username = username | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if username != previousUsername { | ||||||
|  | 			_, err := db.GetUserByName(username) | ||||||
|  | 			if err == nil { | ||||||
|  | 				return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if password != "" { | 		if password != "" { | ||||||
| 			hash, err := util.HashPassword(password) | 			hash, err := util.HashPassword(password) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -158,12 +226,96 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { | ||||||
| 			user.PasswordHash = hash | 			user.PasswordHash = hash | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if previousUsername != currentUser(c) { | ||||||
|  | 			user.Admin = admin | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := db.DeleteUser(previousUsername); err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||||
|  | 		} | ||||||
| 		if err := db.SaveUser(user); err != nil { | 		if err := db.SaveUser(user); err != nil { | ||||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||||
| 		} | 		} | ||||||
| 		log.Infof("Updated admin user information successfully") | 		log.Infof("Updated user information successfully") | ||||||
| 
 | 
 | ||||||
| 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated admin user information successfully"}) | 		if previousUsername == currentUser(c) { | ||||||
|  | 			setUser(c, user.Username, user.Admin) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated user information successfully"}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreateUser to create new user
 | ||||||
|  | func CreateUser(db store.IStore) echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		data := make(map[string]interface{}) | ||||||
|  | 		err := json.NewDecoder(c.Request().Body).Decode(&data) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var user model.User | ||||||
|  | 		username := data["username"].(string) | ||||||
|  | 		password := data["password"].(string) | ||||||
|  | 		admin := data["admin"].(bool) | ||||||
|  | 
 | ||||||
|  | 		if username == "" { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) | ||||||
|  | 		} else { | ||||||
|  | 			user.Username = username | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		{ | ||||||
|  | 			_, err := db.GetUserByName(username) | ||||||
|  | 			if err == nil { | ||||||
|  | 				return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		hash, err := util.HashPassword(password) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||||
|  | 		} | ||||||
|  | 		user.PasswordHash = hash | ||||||
|  | 
 | ||||||
|  | 		user.Admin = admin | ||||||
|  | 
 | ||||||
|  | 		if err := db.SaveUser(user); err != nil { | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||||
|  | 		} | ||||||
|  | 		log.Infof("Created user successfully") | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Created user successfully"}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RemoveUser handler
 | ||||||
|  | func RemoveUser(db store.IStore) echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		data := make(map[string]interface{}) | ||||||
|  | 		err := json.NewDecoder(c.Request().Body).Decode(&data) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		username := data["username"].(string) | ||||||
|  | 
 | ||||||
|  | 		if username == currentUser(c) { | ||||||
|  | 			return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"}) | ||||||
|  | 		} | ||||||
|  | 		// delete user from database
 | ||||||
|  | 
 | ||||||
|  | 		if err := db.DeleteUser(username); err != nil { | ||||||
|  | 			log.Error("Cannot delete user: ", err) | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot delete user from database"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log.Infof("Removed user: %s", username) | ||||||
|  | 
 | ||||||
|  | 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "User removed"}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -179,7 +331,7 @@ func WireGuardClients(db store.IStore) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ | 		return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ | ||||||
| 			"baseData":       model.BaseData{Active: "", CurrentUser: currentUser(c)}, | 			"baseData":       model.BaseData{Active: "", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"clientDataList": clientDataList, | 			"clientDataList": clientDataList, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  | @ -532,7 +684,7 @@ func WireGuardServer(db store.IStore) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return c.Render(http.StatusOK, "server.html", map[string]interface{}{ | 		return c.Render(http.StatusOK, "server.html", map[string]interface{}{ | ||||||
| 			"baseData":        model.BaseData{Active: "wg-server", CurrentUser: currentUser(c)}, | 			"baseData":        model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"serverInterface": server.Interface, | 			"serverInterface": server.Interface, | ||||||
| 			"serverKeyPair":   server.KeyPair, | 			"serverKeyPair":   server.KeyPair, | ||||||
| 		}) | 		}) | ||||||
|  | @ -600,7 +752,7 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{ | 		return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{ | ||||||
| 			"baseData":       model.BaseData{Active: "global-settings", CurrentUser: currentUser(c)}, | 			"baseData":       model.BaseData{Active: "global-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"globalSettings": globalSettings, | 			"globalSettings": globalSettings, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  | @ -630,7 +782,7 @@ func Status(db store.IStore) echo.HandlerFunc { | ||||||
| 		wgClient, err := wgctrl.New() | 		wgClient, err := wgctrl.New() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | 			return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | ||||||
| 				"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, | 				"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 				"error":    err.Error(), | 				"error":    err.Error(), | ||||||
| 				"devices":  nil, | 				"devices":  nil, | ||||||
| 			}) | 			}) | ||||||
|  | @ -639,7 +791,7 @@ func Status(db store.IStore) echo.HandlerFunc { | ||||||
| 		devices, err := wgClient.Devices() | 		devices, err := wgClient.Devices() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | 			return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | ||||||
| 				"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, | 				"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 				"error":    err.Error(), | 				"error":    err.Error(), | ||||||
| 				"devices":  nil, | 				"devices":  nil, | ||||||
| 			}) | 			}) | ||||||
|  | @ -651,7 +803,7 @@ func Status(db store.IStore) echo.HandlerFunc { | ||||||
| 			clients, err := db.GetClients(false) | 			clients, err := db.GetClients(false) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | 				return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ | ||||||
| 					"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, | 					"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 					"error":    err.Error(), | 					"error":    err.Error(), | ||||||
| 					"devices":  nil, | 					"devices":  nil, | ||||||
| 				}) | 				}) | ||||||
|  | @ -697,7 +849,7 @@ func Status(db store.IStore) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return c.Render(http.StatusOK, "status.html", map[string]interface{}{ | 		return c.Render(http.StatusOK, "status.html", map[string]interface{}{ | ||||||
| 			"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, | 			"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"devices":  devicesVm, | 			"devices":  devicesVm, | ||||||
| 			"error":    "", | 			"error":    "", | ||||||
| 		}) | 		}) | ||||||
|  | @ -811,6 +963,12 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { | ||||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"}) | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"}) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		users, err := db.GetUsers() | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error("Cannot get users config: ", err) | ||||||
|  | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get users config"}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		settings, err := db.GetGlobalSettings() | 		settings, err := db.GetGlobalSettings() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Error("Cannot get global settings: ", err) | 			log.Error("Cannot get global settings: ", err) | ||||||
|  | @ -818,7 +976,7 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Write config file
 | 		// Write config file
 | ||||||
| 		err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings) | 		err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Error("Cannot apply server config: ", err) | 			log.Error("Cannot apply server config: ", err) | ||||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ | 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ func GetWakeOnLanHosts(db store.IStore) echo.HandlerFunc { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		err = c.Render(http.StatusOK, "wake_on_lan_hosts.html", map[string]interface{}{ | 		err = c.Render(http.StatusOK, "wake_on_lan_hosts.html", map[string]interface{}{ | ||||||
| 			"baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c)}, | 			"baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c), Admin: isAdmin(c)}, | ||||||
| 			"hosts":    hosts, | 			"hosts":    hosts, | ||||||
| 			"error":    "", | 			"error":    "", | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -14,15 +14,24 @@ func ValidSession(next echo.HandlerFunc) echo.HandlerFunc { | ||||||
| 		if !isValidSession(c) { | 		if !isValidSession(c) { | ||||||
| 			nextURL := c.Request().URL | 			nextURL := c.Request().URL | ||||||
| 			if nextURL != nil && c.Request().Method == http.MethodGet { | 			if nextURL != nil && c.Request().Method == http.MethodGet { | ||||||
| 				return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath + "/login?next=%s", c.Request().URL)) | 				return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath+"/login?next=%s", c.Request().URL)) | ||||||
| 			} else { | 			} else { | ||||||
| 				return c.Redirect(http.StatusTemporaryRedirect, util.BasePath + "/login") | 				return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/login") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		return next(c) | 		return next(c) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func NeedsAdmin(next echo.HandlerFunc) echo.HandlerFunc { | ||||||
|  | 	return func(c echo.Context) error { | ||||||
|  | 		if !isAdmin(c) { | ||||||
|  | 			return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/") | ||||||
|  | 		} | ||||||
|  | 		return next(c) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func isValidSession(c echo.Context) bool { | func isValidSession(c echo.Context) bool { | ||||||
| 	if util.DisableLogin { | 	if util.DisableLogin { | ||||||
| 		return true | 		return true | ||||||
|  | @ -46,10 +55,29 @@ func currentUser(c echo.Context) string { | ||||||
| 	return username | 	return username | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // isAdmin to get user type: admin or manager
 | ||||||
|  | func isAdmin(c echo.Context) bool { | ||||||
|  | 	if util.DisableLogin { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sess, _ := session.Get("session", c) | ||||||
|  | 	admin := fmt.Sprintf("%t", sess.Values["admin"]) | ||||||
|  | 	return admin == "true" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func setUser(c echo.Context, username string, admin bool) { | ||||||
|  | 	sess, _ := session.Get("session", c) | ||||||
|  | 	sess.Values["username"] = username | ||||||
|  | 	sess.Values["admin"] = admin | ||||||
|  | 	sess.Save(c.Request(), c.Response()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // clearSession to remove current session
 | // clearSession to remove current session
 | ||||||
| func clearSession(c echo.Context) { | func clearSession(c echo.Context) { | ||||||
| 	sess, _ := session.Get("session", c) | 	sess, _ := session.Get("session", c) | ||||||
| 	sess.Values["username"] = "" | 	sess.Values["username"] = "" | ||||||
|  | 	sess.Values["admin"] = false | ||||||
| 	sess.Values["session_token"] = "" | 	sess.Values["session_token"] = "" | ||||||
| 	sess.Save(c.Request(), c.Response()) | 	sess.Save(c.Request(), c.Response()) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								main.go
								
								
								
								
							
							
						
						
									
										25
									
								
								main.go
								
								
								
								
							|  | @ -137,7 +137,12 @@ func main() { | ||||||
| 		app.POST(util.BasePath+"/login", handler.Login(db)) | 		app.POST(util.BasePath+"/login", handler.Login(db)) | ||||||
| 		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.POST(util.BasePath+"/profile", handler.UpdateProfile(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.GET(util.BasePath+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin) | ||||||
|  | 		app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var sendmail emailer.Emailer | 	var sendmail emailer.Emailer | ||||||
|  | @ -156,11 +161,12 @@ func main() { | ||||||
| 	app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||||
| 	app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) | 	app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) | ||||||
| 	app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession) | 	app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession, handler.NeedsAdmin) | ||||||
| 	app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) | ||||||
| 	app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson) | 	app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) | ||||||
| 	app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession) | 	app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.NeedsAdmin) | ||||||
| 	app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson) | 
 | ||||||
|  | 	app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) | ||||||
| 	app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession) | 	app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession) | ||||||
| 	app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) | 	app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) | ||||||
| 	app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) | 	app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) | ||||||
|  | @ -199,8 +205,13 @@ func initServerConfig(db store.IStore, tmplBox *rice.Box) { | ||||||
| 		log.Fatalf("Cannot get client config: ", err) | 		log.Fatalf("Cannot get client config: ", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	users, err := db.GetUsers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Cannot get user config: ", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// write config file
 | 	// write config file
 | ||||||
| 	err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings) | 	err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatalf("Cannot create server config: ", err) | 		log.Fatalf("Cannot create server config: ", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -10,4 +10,5 @@ type Interface struct { | ||||||
| type BaseData struct { | type BaseData struct { | ||||||
| 	Active      string | 	Active      string | ||||||
| 	CurrentUser string | 	CurrentUser string | ||||||
|  | 	Admin       bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,4 +6,5 @@ type User struct { | ||||||
| 	Password string `json:"password"` | 	Password string `json:"password"` | ||||||
| 	// PasswordHash takes precedence over Password.
 | 	// PasswordHash takes precedence over Password.
 | ||||||
| 	PasswordHash string `json:"password_hash"` | 	PasswordHash string `json:"password_hash"` | ||||||
|  | 	Admin        bool   `json:"admin"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -83,6 +83,11 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec | ||||||
| 		log.Fatal(err) | 		log.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	tmplUsersSettingsString, err := tmplBox.String("users_settings.html") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	tmplStatusString, err := tmplBox.String("status.html") | 	tmplStatusString, err := tmplBox.String("status.html") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatal(err) | 		log.Fatal(err) | ||||||
|  | @ -108,6 +113,7 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec | ||||||
| 	templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString)) | 	templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString)) | ||||||
| 	templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString)) | 	templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString)) | ||||||
| 	templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString)) | 	templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString)) | ||||||
|  | 	templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString)) | ||||||
| 	templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString)) | 	templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString)) | ||||||
| 	templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString)) | 	templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString)) | ||||||
| 	templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString)) | 	templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString)) | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ func (o *JsonDB) Init() error { | ||||||
| 	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 userPath string = path.Join(serverPath, "users.json") | 	var userPath string = path.Join(o.dbPath, "users") | ||||||
| 	// 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) { | ||||||
| 		os.MkdirAll(clientPath, os.ModePerm) | 		os.MkdirAll(clientPath, os.ModePerm) | ||||||
|  | @ -53,6 +53,9 @@ func (o *JsonDB) Init() error { | ||||||
| 	if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { | 	if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { | ||||||
| 		os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) | 		os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) | ||||||
| 	} | 	} | ||||||
|  | 	if _, err := os.Stat(userPath); os.IsNotExist(err) { | ||||||
|  | 		os.MkdirAll(userPath, os.ModePerm) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// server's interface
 | 	// server's interface
 | ||||||
| 	if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { | 	if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { | ||||||
|  | @ -103,9 +106,11 @@ func (o *JsonDB) Init() error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// user info
 | 	// user info
 | ||||||
| 	if _, err := os.Stat(userPath); os.IsNotExist(err) { | 	results, err := o.conn.ReadAll("users") | ||||||
|  | 	if err != nil || len(results) < 1 { | ||||||
| 		user := new(model.User) | 		user := new(model.User) | ||||||
| 		user.Username = util.LookupEnvOrString(util.UsernameEnvVar, util.DefaultUsername) | 		user.Username = util.LookupEnvOrString(util.UsernameEnvVar, util.DefaultUsername) | ||||||
|  | 		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) | 			plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) | ||||||
|  | @ -115,7 +120,7 @@ func (o *JsonDB) Init() error { | ||||||
| 			} | 			} | ||||||
| 			user.PasswordHash = hash | 			user.PasswordHash = hash | ||||||
| 		} | 		} | ||||||
| 		o.conn.Write("server", "users", user) | 		o.conn.Write("users", user.Username, user) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -127,9 +132,44 @@ func (o *JsonDB) GetUser() (model.User, error) { | ||||||
| 	return user, o.conn.Read("server", "users", &user) | 	return user, o.conn.Read("server", "users", &user) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SaveUser func to user info to the database
 | // GetUsers func to get all users from the database
 | ||||||
|  | func (o *JsonDB) GetUsers() ([]model.User, error) { | ||||||
|  | 	var users []model.User | ||||||
|  | 	results, err := o.conn.ReadAll("users") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return users, err | ||||||
|  | 	} | ||||||
|  | 	for _, i := range results { | ||||||
|  | 		user := model.User{} | ||||||
|  | 
 | ||||||
|  | 		if err := json.Unmarshal([]byte(i), &user); err != nil { | ||||||
|  | 			return users, fmt.Errorf("cannot decode user json structure: %v", err) | ||||||
|  | 		} | ||||||
|  | 		users = append(users, user) | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 	return users, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetUserByName func to get single user from the database
 | ||||||
|  | func (o *JsonDB) GetUserByName(username string) (model.User, error) { | ||||||
|  | 	user := model.User{} | ||||||
|  | 
 | ||||||
|  | 	if err := o.conn.Read("users", username, &user); err != nil { | ||||||
|  | 		return user, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return user, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SaveUser func to save user in the database
 | ||||||
| func (o *JsonDB) SaveUser(user model.User) error { | func (o *JsonDB) SaveUser(user model.User) error { | ||||||
| 	return o.conn.Write("server", "users", user) | 	return o.conn.Write("users", user.Username, user) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeleteUser func to remove user from the database
 | ||||||
|  | func (o *JsonDB) DeleteUser(username string) error { | ||||||
|  | 	return o.conn.Delete("users", username) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetGlobalSettings func to query global settings from the database
 | // GetGlobalSettings func to query global settings from the database
 | ||||||
|  | @ -213,7 +253,7 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti | ||||||
| 		server, _ := o.GetServer() | 		server, _ := o.GetServer() | ||||||
| 		globalSettings, _ := o.GetGlobalSettings() | 		globalSettings, _ := o.GetGlobalSettings() | ||||||
| 		client := client | 		client := client | ||||||
| 		if !qrCodeSettings.IncludeDNS{ | 		if !qrCodeSettings.IncludeDNS { | ||||||
| 			globalSettings.DNSServers = []string{} | 			globalSettings.DNSServers = []string{} | ||||||
| 		} | 		} | ||||||
| 		if !qrCodeSettings.IncludeMTU { | 		if !qrCodeSettings.IncludeMTU { | ||||||
|  |  | ||||||
|  | @ -6,8 +6,10 @@ import ( | ||||||
| 
 | 
 | ||||||
| type IStore interface { | type IStore interface { | ||||||
| 	Init() error | 	Init() error | ||||||
| 	GetUser() (model.User, error) | 	GetUsers() ([]model.User, error) | ||||||
|  | 	GetUserByName(username string) (model.User, error) | ||||||
| 	SaveUser(user model.User) error | 	SaveUser(user model.User) error | ||||||
|  | 	DeleteUser(username string) error | ||||||
| 	GetGlobalSettings() (model.GlobalSetting, error) | 	GetGlobalSettings() (model.GlobalSetting, error) | ||||||
| 	GetServer() (model.Server, error) | 	GetServer() (model.Server, error) | ||||||
| 	GetClients(hasQRCode bool) ([]model.ClientData, error) | 	GetClients(hasQRCode bool) ([]model.ClientData, error) | ||||||
|  |  | ||||||
|  | @ -90,7 +90,13 @@ | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="info"> |                     <div class="info"> | ||||||
|                         {{if .baseData.CurrentUser}} |                         {{if .baseData.CurrentUser}} | ||||||
|                         <a href="{{.basePath}}/profile" class="d-block">{{.baseData.CurrentUser}}</a> | 
 | ||||||
|  |                         {{if .baseData.Admin}} | ||||||
|  |                         <a href="{{.basePath}}/profile" class="d-block">Administrator: {{.baseData.CurrentUser}}</a> | ||||||
|  |                         {{else}} | ||||||
|  |                         <a href="{{.basePath}}/profile" class="d-block">Manager: {{.baseData.CurrentUser}}</a> | ||||||
|  |                         {{end}} | ||||||
|  | 
 | ||||||
|                         {{else}} |                         {{else}} | ||||||
|                         <a href="#" class="d-block">Administrator</a> |                         <a href="#" class="d-block">Administrator</a> | ||||||
|                         {{end}} |                         {{end}} | ||||||
|  | @ -109,6 +115,8 @@ | ||||||
|                                 </p> |                                 </p> | ||||||
|                             </a> |                             </a> | ||||||
|                         </li> |                         </li> | ||||||
|  | 
 | ||||||
|  |                         {{if .baseData.Admin}} | ||||||
|                         <li class="nav-item"> |                         <li class="nav-item"> | ||||||
|                             <a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}"> |                             <a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}"> | ||||||
|                                 <i class="nav-icon fas fa-server"></i> |                                 <i class="nav-icon fas fa-server"></i> | ||||||
|  | @ -117,6 +125,8 @@ | ||||||
|                                 </p> |                                 </p> | ||||||
|                             </a> |                             </a> | ||||||
|                         </li> |                         </li> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|                         <li class="nav-header">SETTINGS</li> |                         <li class="nav-header">SETTINGS</li> | ||||||
|                         <li class="nav-item"> |                         <li class="nav-item"> | ||||||
|                             <a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}"> |                             <a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}"> | ||||||
|  | @ -126,6 +136,16 @@ | ||||||
|                                 </p> |                                 </p> | ||||||
|                             </a> |                             </a> | ||||||
|                         </li> |                         </li> | ||||||
|  |                         <li class="nav-item"> | ||||||
|  |                             <a href="{{.basePath}}/users-settings" class="nav-link {{if eq .baseData.Active "users-settings" }}active{{end}}"> | ||||||
|  |                             <i class="nav-icon fas fa-cog"></i> | ||||||
|  |                             <p> | ||||||
|  |                                 Users Settings | ||||||
|  |                             </p> | ||||||
|  |                             </a> | ||||||
|  |                         </li> | ||||||
|  |                         {{end}} | ||||||
|  | 
 | ||||||
|                         <li class="nav-header">UTILITIES</li> |                         <li class="nav-header">UTILITIES</li> | ||||||
|                         <li class="nav-item"> |                         <li class="nav-item"> | ||||||
|                             <a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}"> |                             <a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}"> | ||||||
|  |  | ||||||
|  | @ -101,7 +101,11 @@ | ||||||
|         $("#btn_login").click(function () { |         $("#btn_login").click(function () { | ||||||
|             const username = $("#username").val(); |             const username = $("#username").val(); | ||||||
|             const password = $("#password").val(); |             const password = $("#password").val(); | ||||||
|             const data = {"username": username, "password": password} |             let rememberMe = false; | ||||||
|  |             if ($("#remember").is(':checked')){ | ||||||
|  |                 rememberMe = true; | ||||||
|  |             } | ||||||
|  |             const data = {"username": username, "password": password, "rememberMe": rememberMe} | ||||||
| 
 | 
 | ||||||
|             $.ajax({ |             $.ajax({ | ||||||
|                 cache: false, |                 cache: false, | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ Profile | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
|                                 <label for="username" class="control-label">Username</label> |                                 <label for="username" class="control-label">Username</label> | ||||||
|                                 <input type="text" class="form-control" name="username" id="username" |                                 <input type="text" class="form-control" name="username" id="username" | ||||||
|                                        value="{{ .userInfo.Username }}"> |                                        value=""> | ||||||
|                             </div> |                             </div> | ||||||
|                             <div class="form-group"> |                             <div class="form-group"> | ||||||
|                                 <label for="password" class="control-label">Password</label> |                                 <label for="password" class="control-label">Password</label> | ||||||
|  | @ -55,56 +55,82 @@ Profile | ||||||
| 
 | 
 | ||||||
| {{ define "bottom_js"}} | {{ define "bottom_js"}} | ||||||
| <script> | <script> | ||||||
|   function updateUserInfo() { |     { | ||||||
|     const username = $("#username").val(); |         var previous_username; | ||||||
|     const password = $("#password").val(); |         var admin; | ||||||
|     const data = {"username": username, "password": password}; |     } | ||||||
|     $.ajax({ |     $(document).ready(function () { | ||||||
|       cache: false, |         $.ajax({ | ||||||
|       method: 'POST', |             cache: false, | ||||||
|       url: '{{.basePath}}/profile', |             method: 'GET', | ||||||
|       dataType: 'json', |             url: '{{.basePath}}/api/user/{{.baseData.CurrentUser}}', | ||||||
|       contentType: "application/json", |             dataType: 'json', | ||||||
|       data: JSON.stringify(data), |             contentType: "application/json", | ||||||
|       success: function (data) { |             success: function (resp) { | ||||||
|         toastr.success("Updated admin user information successfully"); |                 const user = resp; | ||||||
|       }, |                 $("#username").val(user.username); | ||||||
|       error: function (jqXHR, exception) { |                 previous_username = user.username; | ||||||
|         const responseJson = jQuery.parseJSON(jqXHR.responseText); |                 admin = user.admin; | ||||||
|         toastr.error(responseJson['message']); |             }, | ||||||
|       } |             error: function (jqXHR, exception) { | ||||||
|  |                 const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                 toastr.error(responseJson['message']); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     }); |     }); | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   $(document).ready(function () { | 
 | ||||||
|     $.validator.setDefaults({ |     function updateUserInfo() { | ||||||
|       submitHandler: function () { |         const username = $("#username").val(); | ||||||
|         updateUserInfo(); |         const password = $("#password").val(); | ||||||
|       } |         const data = {"username": username, "password": password, "previous_username": previous_username, "admin":admin}; | ||||||
|  |         $.ajax({ | ||||||
|  |             cache: false, | ||||||
|  |             method: 'POST', | ||||||
|  |             url: '{{.basePath}}/update-user', | ||||||
|  |             dataType: 'json', | ||||||
|  |             contentType: "application/json", | ||||||
|  |             data: JSON.stringify(data), | ||||||
|  |             success: function (data) { | ||||||
|  |                 toastr.success("Updated user information successfully"); | ||||||
|  |                 location.reload(); | ||||||
|  |             }, | ||||||
|  |             error: function (jqXHR, exception) { | ||||||
|  |                 const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                 toastr.error(responseJson['message']); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         $.validator.setDefaults({ | ||||||
|  |             submitHandler: function () { | ||||||
|  |                 updateUserInfo(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         $("#frm_profile").validate({ | ||||||
|  |             rules: { | ||||||
|  |                 username: { | ||||||
|  |                     required: true | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             messages: { | ||||||
|  |                 username: { | ||||||
|  |                     required: "Please enter a username", | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             errorElement: 'span', | ||||||
|  |             errorPlacement: function (error, element) { | ||||||
|  |                 error.addClass('invalid-feedback'); | ||||||
|  |                 element.closest('.form-group').append(error); | ||||||
|  |             }, | ||||||
|  |             highlight: function (element, errorClass, validClass) { | ||||||
|  |                 $(element).addClass('is-invalid'); | ||||||
|  |             }, | ||||||
|  |             unhighlight: function (element, errorClass, validClass) { | ||||||
|  |                 $(element).removeClass('is-invalid'); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     }); |     }); | ||||||
|     $("#frm_profile").validate({ |  | ||||||
|       rules: { |  | ||||||
|         username: { |  | ||||||
|           required: true |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       messages: { |  | ||||||
|         username: { |  | ||||||
|           required: "Please enter a username", |  | ||||||
|         } |  | ||||||
|       }, |  | ||||||
|       errorElement: 'span', |  | ||||||
|       errorPlacement: function (error, element) { |  | ||||||
|         error.addClass('invalid-feedback'); |  | ||||||
|         element.closest('.form-group').append(error); |  | ||||||
|       }, |  | ||||||
|       highlight: function (element, errorClass, validClass) { |  | ||||||
|         $(element).addClass('is-invalid'); |  | ||||||
|       }, |  | ||||||
|       unhighlight: function (element, errorClass, validClass) { |  | ||||||
|         $(element).removeClass('is-invalid'); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| </script> | </script> | ||||||
| {{ end }} | {{ end }} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,294 @@ | ||||||
|  | {{define "title"}} | ||||||
|  | Users Settings | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "top_css"}} | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "username"}} | ||||||
|  | {{ .username }} | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "page_title"}} | ||||||
|  | Users Settings | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "page_content"}} | ||||||
|  | <section class="content"> | ||||||
|  |     <div class="container-fluid"> | ||||||
|  |         <div class="row" id="users-list"> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | 
 | ||||||
|  | <div class="modal fade" id="modal_edit_user"> | ||||||
|  |     <div class="modal-dialog"> | ||||||
|  |         <div class="modal-content"> | ||||||
|  |             <div class="modal-header"> | ||||||
|  |                 <h4 class="modal-title">Edit User</h4> | ||||||
|  |                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|  |                     <span aria-hidden="true">×</span> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |             <form name="frm_edit_user" id="frm_edit_user"> | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     <div class="form-group" style="display:none"> | ||||||
|  |                         <input type="text" style="display:none" class="form-control" id="_previous_user_name" | ||||||
|  |                                name="_previous_user_name"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <label for="_user_name" class="control-label">Name</label> | ||||||
|  |                         <input type="text" class="form-control" id="_user_name" name="_user_name"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <label for="_user_password" class="control-label">Password</label> | ||||||
|  |                         <input type="text" class="form-control" id="_user_password" name="_user_password" value="" | ||||||
|  |                                placeholder="Leave empty to keep the password unchanged"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="form-group"> | ||||||
|  |                         <div class="icheck-primary d-inline"> | ||||||
|  |                             <input type="checkbox" id="_admin"> | ||||||
|  |                             <label for="_admin"> | ||||||
|  |                                 Admin | ||||||
|  |                             </label> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-footer justify-content-between"> | ||||||
|  |                     <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||||||
|  |                     <button type="submit" class="btn btn-success">Save</button> | ||||||
|  |                 </div> | ||||||
|  |             </form> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.modal-content --> | ||||||
|  |     </div> | ||||||
|  |     <!-- /.modal-dialog --> | ||||||
|  | </div> | ||||||
|  | <!-- /.modal --> | ||||||
|  | 
 | ||||||
|  | <div class="modal fade" id="modal_remove_user"> | ||||||
|  |     <div class="modal-dialog"> | ||||||
|  |         <div class="modal-content bg-danger"> | ||||||
|  |             <div class="modal-header"> | ||||||
|  |                 <h4 class="modal-title">Remove</h4> | ||||||
|  |                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|  |                     <span aria-hidden="true">×</span> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |             <div class="modal-body"> | ||||||
|  |             </div> | ||||||
|  |             <div class="modal-footer justify-content-between"> | ||||||
|  |                 <button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button> | ||||||
|  |                 <button type="button" class="btn btn-outline-dark" id="remove_user_confirm">Apply</button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <!-- /.modal-content --> | ||||||
|  |     </div> | ||||||
|  |     <!-- /.modal-dialog --> | ||||||
|  | </div> | ||||||
|  | <!-- /.modal --> | ||||||
|  | {{end}} | ||||||
|  | 
 | ||||||
|  | {{define "bottom_js"}} | ||||||
|  | <script> | ||||||
|  |     function populateUsersList() { | ||||||
|  |         $.ajax({ | ||||||
|  |             cache: false, | ||||||
|  |             method: 'GET', | ||||||
|  |             url: '{{.basePath}}/getusers', | ||||||
|  |             dataType: 'json', | ||||||
|  |             contentType: "application/json", | ||||||
|  |             success: function (data) { | ||||||
|  |                 renderUserList(data); | ||||||
|  |             }, | ||||||
|  |             error: function (jqXHR, exception) { | ||||||
|  |                 const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                 toastr.error(responseJson['message']); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | </script> | ||||||
|  | <script> | ||||||
|  |     // load user list | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         populateUsersList(); | ||||||
|  |         let newUserHtml = '<div class="col-sm-2 offset-md-4" style=" text-align: right;">' + | ||||||
|  |             '<button style="" id="btn_new_user" type="button" class="btn btn-outline-primary btn-sm" ' + | ||||||
|  |             'data-toggle="modal" data-target="#modal_edit_user" data-username="">' + | ||||||
|  |             '<i class="nav-icon fas fa-plus"></i> New User</button></div>'; | ||||||
|  |         $('h1').parents(".row").append(newUserHtml); | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     // modal_remove_user modal event | ||||||
|  |     $("#modal_remove_user").on('show.bs.modal', function (event) { | ||||||
|  |         const button = $(event.relatedTarget); | ||||||
|  |         const user_name = button.data('username'); | ||||||
|  |         const modal = $(this); | ||||||
|  |         modal.find('.modal-body').text("You are about to remove user " + user_name); | ||||||
|  |         modal.find('#remove_user_confirm').val(user_name); | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     // remove_user_confirm button event | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         $("#remove_user_confirm").click(function () { | ||||||
|  |             const user_name = $(this).val(); | ||||||
|  |             const data = {"username": user_name}; | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'POST', | ||||||
|  |                 url: '{{.basePath}}/remove-user', | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 data: JSON.stringify(data), | ||||||
|  |                 success: function (data) { | ||||||
|  |                     $("#modal_remove_user").modal('hide'); | ||||||
|  |                     toastr.success('Removed user successfully'); | ||||||
|  |                     const divElement = document.getElementById('user_' + user_name); | ||||||
|  |                     divElement.style.display = "none"; | ||||||
|  |                     location.reload() | ||||||
|  |                 }, | ||||||
|  |                 error: function (jqXHR, exception) { | ||||||
|  |                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                     toastr.error(responseJson['message']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Edit user modal event | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         $("#modal_edit_user").on('show.bs.modal', function (event) { | ||||||
|  |             let modal = $(this); | ||||||
|  |             const button = $(event.relatedTarget); | ||||||
|  |             const user_name = button.data('username'); | ||||||
|  | 
 | ||||||
|  |             // update user modal data | ||||||
|  |             if (user_name !== "") { | ||||||
|  |                 $.ajax({ | ||||||
|  |                     cache: false, | ||||||
|  |                     method: 'GET', | ||||||
|  |                     url: '{{.basePath}}/api/user/' + user_name, | ||||||
|  |                     dataType: 'json', | ||||||
|  |                     contentType: "application/json", | ||||||
|  |                     success: function (resp) { | ||||||
|  |                         const user = resp; | ||||||
|  | 
 | ||||||
|  |                         modal.find(".modal-title").text("Edit user " + user.username); | ||||||
|  |                         modal.find("#_user_name").val(user.username); | ||||||
|  |                         modal.find("#_previous_user_name").val(user.username); | ||||||
|  |                         modal.find("#_user_password").val(""); | ||||||
|  |                         modal.find("#_user_password").prop("placeholder", "Leave empty to keep the password unchanged") | ||||||
|  |                         modal.find("#_admin").prop("checked", user.admin); | ||||||
|  |                     }, | ||||||
|  |                     error: function (jqXHR, exception) { | ||||||
|  |                         const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                         toastr.error(responseJson['message']); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } else { | ||||||
|  |                 modal.find(".modal-title").text("Add new user"); | ||||||
|  |                 modal.find("#_user_name").val(""); | ||||||
|  |                 modal.find("#_previous_user_name").val(""); | ||||||
|  |                 modal.find("#_user_password").val(""); | ||||||
|  |                 modal.find("#_user_password").prop("placeholder", "") | ||||||
|  |                 modal.find("#_admin").prop("checked", false); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     function updateUserInfo() { | ||||||
|  |         const username = $("#_user_name").val(); | ||||||
|  |         const previous_username = $("#_previous_user_name").val(); | ||||||
|  |         const password = $("#_user_password").val(); | ||||||
|  |         let admin = false; | ||||||
|  |         if ($("#_admin").is(':checked')) { | ||||||
|  |             admin = true; | ||||||
|  |         } | ||||||
|  |         const data = { | ||||||
|  |             "username": username, | ||||||
|  |             "password": password, | ||||||
|  |             "previous_username": previous_username, | ||||||
|  |             "admin": admin | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if (previous_username !== "") { | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'POST', | ||||||
|  |                 url: '{{.basePath}}/update-user', | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 data: JSON.stringify(data), | ||||||
|  |                 success: function (data) { | ||||||
|  |                     toastr.success("Updated user information successfully"); | ||||||
|  |                     location.reload(); | ||||||
|  |                 }, | ||||||
|  |                 error: function (jqXHR, exception) { | ||||||
|  |                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                     toastr.error(responseJson['message']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             $.ajax({ | ||||||
|  |                 cache: false, | ||||||
|  |                 method: 'POST', | ||||||
|  |                 url: '{{.basePath}}/create-user', | ||||||
|  |                 dataType: 'json', | ||||||
|  |                 contentType: "application/json", | ||||||
|  |                 data: JSON.stringify(data), | ||||||
|  |                 success: function (data) { | ||||||
|  |                     toastr.success("Created user successfully"); | ||||||
|  |                     location.reload(); | ||||||
|  |                 }, | ||||||
|  |                 error: function (jqXHR, exception) { | ||||||
|  |                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||||
|  |                     toastr.error(responseJson['message']); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         $.validator.setDefaults({ | ||||||
|  |             submitHandler: function (form) { | ||||||
|  |                 updateUserInfo(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         // Edit user form validation | ||||||
|  |         $("#frm_edit_user").validate({ | ||||||
|  |             rules: { | ||||||
|  |                 _user_name: { | ||||||
|  |                     required: true | ||||||
|  |                 }, | ||||||
|  |                 _user_password: { | ||||||
|  |                     required: function () { | ||||||
|  |                         return $("#_previous_user_name").val() === ""; | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             messages: { | ||||||
|  |                 _user_name: { | ||||||
|  |                     required: "Please enter a username" | ||||||
|  |                 }, | ||||||
|  |                 _user_password: { | ||||||
|  |                     required: "Please input a password" | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             errorElement: 'span', | ||||||
|  |             errorPlacement: function (error, element) { | ||||||
|  |                 error.addClass('invalid-feedback'); | ||||||
|  |                 element.closest('.form-group').append(error); | ||||||
|  |             }, | ||||||
|  |             highlight: function (element, errorClass, validClass) { | ||||||
|  |                 $(element).addClass('is-invalid'); | ||||||
|  |             }, | ||||||
|  |             unhighlight: function (element, errorClass, validClass) { | ||||||
|  |                 $(element).removeClass('is-invalid'); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         // | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | {{end}} | ||||||
|  | @ -24,6 +24,7 @@ var ( | ||||||
| const ( | const ( | ||||||
| 	DefaultUsername                        = "admin" | 	DefaultUsername                        = "admin" | ||||||
| 	DefaultPassword                        = "admin" | 	DefaultPassword                        = "admin" | ||||||
|  | 	DefaultIsAdmin                         = true | ||||||
| 	DefaultServerAddress                   = "10.252.1.0/24" | 	DefaultServerAddress                   = "10.252.1.0/24" | ||||||
| 	DefaultServerPort                      = 51820 | 	DefaultServerPort                      = 51820 | ||||||
| 	DefaultDNS                             = "1.1.1.1" | 	DefaultDNS                             = "1.1.1.1" | ||||||
|  |  | ||||||
|  | @ -381,7 +381,7 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
 | // WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
 | ||||||
| func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, clientDataList []model.ClientData, globalSettings model.GlobalSetting) error { | func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error { | ||||||
| 	var tmplWireguardConf string | 	var tmplWireguardConf string | ||||||
| 
 | 
 | ||||||
| 	// if set, read wg.conf template from WgConfTemplate
 | 	// if set, read wg.conf template from WgConfTemplate
 | ||||||
|  | @ -416,6 +416,7 @@ func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, cl | ||||||
| 		"serverConfig":   serverConfig, | 		"serverConfig":   serverConfig, | ||||||
| 		"clientDataList": clientDataList, | 		"clientDataList": clientDataList, | ||||||
| 		"globalSettings": globalSettings, | 		"globalSettings": globalSettings, | ||||||
|  | 		"usersList":      usersList, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = t.Execute(f, config) | 	err = t.Execute(f, config) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue