Add per-server access control (User.ServerIDs)

Non-admin users are now restricted to servers explicitly listed in
their new ServerIDs field; empty means no access (secure by default).
Admins always have full access. Migration backfills existing users'
ServerIDs with the migrated legacy server so nobody is locked out on
upgrade. New RequireServerAccess middleware enforces this on
/servers/:id/... routes (applied to GET /servers/:id/clients so far);
GET /servers also filters its list for non-admins.
This commit is contained in:
sysops
2026-07-11 23:20:52 +02:00
parent a946c059c3
commit 5b72a7c118
5 changed files with 75 additions and 1 deletions
+20
View File
@@ -419,6 +419,26 @@ func ListServers(db store.IStore) echo.HandlerFunc {
false, fmt.Sprintf("Cannot get server list: %v", err),
})
}
// non-admins only see servers they have been explicitly granted
if !util.DisableLogin && !isAdmin(c) {
user, err := db.GetUserByName(currentUser(c))
if err != nil {
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Access denied"})
}
allowed := make(map[string]bool, len(user.ServerIDs))
for _, id := range user.ServerIDs {
allowed[id] = true
}
var filtered []model.Server
for _, s := range servers {
if allowed[s.ID] {
filtered = append(filtered, s)
}
}
servers = filtered
}
return c.JSON(http.StatusOK, servers)
}
}
+29
View File
@@ -8,6 +8,7 @@ import (
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"github.com/ngoduykhanh/wireguard-ui/store"
"github.com/ngoduykhanh/wireguard-ui/util"
)
@@ -43,6 +44,34 @@ func NeedsAdmin(next echo.HandlerFunc) echo.HandlerFunc {
}
}
// RequireServerAccess must only be used after ValidSession middleware.
// It restricts /servers/:id/... routes to admins (always allowed) and to
// non-admin users whose User.ServerIDs contains the requested server ID.
// A user with an empty ServerIDs list has no server access at all - the
// migration backfills existing users so nobody is locked out by this
// change on upgrade.
func RequireServerAccess(db store.IStore) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if util.DisableLogin || isAdmin(c) {
return next(c)
}
serverID := c.Param("id")
user, err := db.GetUserByName(currentUser(c))
if err != nil {
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Access denied"})
}
for _, id := range user.ServerIDs {
if id == serverID {
return next(c)
}
}
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "You do not have access to this server"})
}
}
}
func isValidSession(c echo.Context) bool {
if util.DisableLogin {
return true