Add server creation UI + user-server access assignment (step 3)
New admin-only page at /servers-settings (templates/servers.html) lists servers and creates new ones via POST /servers (ID/name/interface/ addresses/port, key pair generated server-side). Nav gets a "Servers" link. templates/users_settings.html gains a multi-select "Server Access" field wired to the server_ids support added to create-user/update-user in the previous commit, so admins can now actually assign non-admin users to specific servers through the UI.
This commit is contained in:
@@ -199,6 +199,15 @@ func UsersSettings() echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ServersPage renders the server list/create page
|
||||
func ServersPage() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "servers.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "servers", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUser to update user information
|
||||
func UpdateUser(db store.IStore) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
@@ -256,6 +265,19 @@ func UpdateUser(db store.IStore) echo.HandlerFunc {
|
||||
user.Admin = admin
|
||||
}
|
||||
|
||||
// only an admin may change which servers a user can access
|
||||
if isAdmin(c) {
|
||||
if rawIDs, ok := data["server_ids"].([]interface{}); ok {
|
||||
serverIDs := make([]string, 0, len(rawIDs))
|
||||
for _, v := range rawIDs {
|
||||
if id, ok := v.(string); ok && util.ValidateRecordID(id) {
|
||||
serverIDs = append(serverIDs, id)
|
||||
}
|
||||
}
|
||||
user.ServerIDs = serverIDs
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.DeleteUser(previousUsername); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||
}
|
||||
@@ -308,6 +330,14 @@ func CreateUser(db store.IStore) echo.HandlerFunc {
|
||||
|
||||
user.Admin = admin
|
||||
|
||||
if rawIDs, ok := data["server_ids"].([]interface{}); ok {
|
||||
for _, v := range rawIDs {
|
||||
if id, ok := v.(string); ok && util.ValidateRecordID(id) {
|
||||
user.ServerIDs = append(user.ServerIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.SaveUser(user); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||
}
|
||||
@@ -476,6 +506,83 @@ func GetServerClients(db store.IStore) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// CreateServer handler creates a new WireGuard server (step 3 of the
|
||||
// multi-server extension). Admin-only. Generates a fresh key pair,
|
||||
// validates the ID/interface name/subnet, and stores the server plus its
|
||||
// per-server settings.
|
||||
func CreateServer(db store.IStore) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
type createServerRequest struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Interface string `json:"interface"`
|
||||
Addresses []string `json:"addresses"`
|
||||
ListenPort int `json:"listen_port"`
|
||||
}
|
||||
var req createServerRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||
}
|
||||
|
||||
if !util.ValidateRecordID(req.ID) {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid server ID (letters, digits, - and _ only)"})
|
||||
}
|
||||
if !util.ValidateInterfaceName(req.Interface) {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid interface name (max 15 chars, letters/digits/-/_ only)"})
|
||||
}
|
||||
if !util.ValidateServerAddresses(req.Addresses) {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Address ranges must be in CIDR format"})
|
||||
}
|
||||
if req.ListenPort <= 0 || req.ListenPort > 65535 {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid listen port"})
|
||||
}
|
||||
if req.Name == "" {
|
||||
req.Name = req.ID
|
||||
}
|
||||
|
||||
if _, err := db.GetServerByID(req.ID); err == nil {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "A server with this ID already exists"})
|
||||
}
|
||||
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
log.Error("Cannot generate wireguard key pair: ", err)
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"})
|
||||
}
|
||||
|
||||
server := model.Server{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
KeyPair: &model.ServerKeypair{
|
||||
PrivateKey: key.String(),
|
||||
PublicKey: key.PublicKey().String(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
},
|
||||
Interface: &model.ServerInterface{
|
||||
Name: req.Interface,
|
||||
Addresses: req.Addresses,
|
||||
ListenPort: req.ListenPort,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
|
||||
if err := db.CreateServer(server); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot create server: %v", err)})
|
||||
}
|
||||
|
||||
settings := model.ServerSetting{
|
||||
ConfigFilePath: fmt.Sprintf("/etc/wireguard/%s.conf", req.Interface),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := db.SaveServerSettings(req.ID, settings); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Server created but settings failed: %v", err)})
|
||||
}
|
||||
|
||||
log.Infof("Created server %s (%s)", req.ID, req.Name)
|
||||
return c.JSON(http.StatusOK, server)
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient handler
|
||||
func NewClient(db store.IStore) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
||||
Reference in New Issue
Block a user