fixed bugs

This commit is contained in:
ACoolName 2025-03-19 19:56:58 +02:00
parent 1488d7db16
commit 32d64f3637
14 changed files with 173 additions and 98 deletions

View File

@ -48,28 +48,6 @@ func (con *AuthApi) signToken(token Claims) (string, error) {
return t.SignedString([]byte(con.config.Signing.Key))
}
func AuthorizedTo(requiredPermissions models.Permission) gin.HandlerFunc {
return func(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims")
if !exists {
log.Printf("LoggedIn was not called first")
ctx.AbortWithError(500, fmt.Errorf("Misconfigured method"))
return
}
claims, ok := claimsPointer.(*AuthClaims)
if !ok {
ctx.AbortWithStatus(500)
return
}
if (requiredPermissions&claims.Permissions != requiredPermissions) && (models.Admin&claims.Permissions != models.Admin) {
ctx.AbortWithStatusJSON(403, "matching permissions were not found")
return
}
}
}
func (con *AuthApi) LoggedIn(ctx *gin.Context) {
authCookie, err := ctx.Request.Cookie("auth")
if err != nil {
@ -77,7 +55,7 @@ func (con *AuthApi) LoggedIn(ctx *gin.Context) {
return
}
token, err := jwt.ParseWithClaims(authCookie.Value, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.ParseWithClaims(authCookie.Value, &AuthClaims{}, func(token *jwt.Token) (any, error) {
// Don't forget to validate the alg is what you expect:
if token.Method.Alg() != con.config.Signing.Algorithm {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
@ -216,6 +194,7 @@ func (con AuthApi) Verify(ctx *gin.Context) {
ctx.Redirect(303, fmt.Sprintf("http://%s/login", con.config.Domain))
}
func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFunc {
userAuthHandler, err := factories.GetUserPassAuthDbHandler(config.Authentication.UserPass)
if err != nil {
@ -234,7 +213,7 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) gin.HandlerFu
}
group.POST("/signin", connection.signIn)
group.POST("/signup", connection.LoggedIn, AuthorizedTo(models.Admin), connection.signUp)
group.POST("/signup", connection.signUp)
group.Any("/verify", connection.Verify)
return connection.LoggedIn

46
auth/utils.go Normal file
View File

@ -0,0 +1,46 @@
package auth
import (
"fmt"
"git.acooldomain.co/server-manager/backend/models"
"github.com/gin-gonic/gin"
)
const AuthorizedParam string = "authorized"
func AuthorizedTo(requiredPermissions models.Permission) gin.HandlerFunc {
return func(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims")
if !exists {
ctx.AbortWithError(500, fmt.Errorf("Did not call LoggedIn first"))
return
}
claims, ok := claimsPointer.(*AuthClaims)
if !ok {
return
}
if (requiredPermissions&claims.Permissions != requiredPermissions) && (models.Admin&claims.Permissions != models.Admin) {
return
}
ctx.Set(AuthorizedParam, true)
}
}
func AuthorizationEnforcer() gin.HandlerFunc {
return func(ctx *gin.Context) {
authorized, exists := ctx.Get(AuthorizedParam)
if !exists {
ctx.AbortWithStatus(403)
return
}
if !authorized.(bool) {
ctx.AbortWithStatus(403)
}
}
}

View File

@ -106,7 +106,7 @@ func (self *ServersDbHandler) ListServers(ctx context.Context) ([]dbhandler.Serv
func (self *ServersDbHandler) GetServer(ctx context.Context, serverId string) (*dbhandler.Server, error) {
var server Server
err := self.collection.FindOne(ctx, bson.M{"server_id": serverId}).Decode(&server)
err := self.collection.FindOne(ctx, bson.M{"id": serverId}).Decode(&server)
if err != nil {
return nil, err
}
@ -125,7 +125,7 @@ func (self *ServersDbHandler) CreateServer(ctx context.Context, server dbhandler
func (self *ServersDbHandler) DeleteServer(ctx context.Context, serverId string) error {
_, err := self.collection.DeleteOne(ctx, bson.M{
"server_id": serverId,
"id": serverId,
})
return err
@ -163,7 +163,7 @@ func (self *ServersDbHandler) UpdateServer(ctx context.Context, serverId string,
updateServerRequest["command"] = updateParams.Command
}
_, err := self.collection.UpdateOne(ctx, bson.M{"server_id": serverId}, bson.M{"$set": updateServerRequest})
_, err := self.collection.UpdateOne(ctx, bson.M{"id": serverId}, bson.M{"$set": updateServerRequest})
return err
}

View File

@ -98,6 +98,14 @@ func (self *UserPassAuthenticationDbHandler) CreateUser(
return err
}
func (self *UserPassAuthenticationDbHandler) CountUsers(ctx context.Context) (uint, error) {
count, err := self.collection.CountDocuments(ctx, bson.M{})
if err != nil {
return 0, err
}
return uint(count), nil
}
func (self *UserPassAuthenticationDbHandler) RemoveUser(ctx context.Context, username string) error {
_, err := self.collection.DeleteOne(
ctx,

View File

@ -28,6 +28,7 @@ type UserPassAuthanticationDbHandler interface {
// Read Only
AuthenticateUser(ctx context.Context, username string, password string) (*models.User, error)
ListUsers(ctx context.Context) ([]models.User, error)
CountUsers(ctx context.Context) (uint, error)
// Write
CreateUser(ctx context.Context, username string, password string, permissions models.Permission, email string, maxOwnedServers uint) error

View File

@ -1,11 +1,14 @@
package factories
import (
"context"
"errors"
"log"
"sync"
"time"
"git.acooldomain.co/server-manager/backend/dbhandler/mongo"
"git.acooldomain.co/server-manager/backend/dbhandler"
"git.acooldomain.co/server-manager/backend/dbhandler/mongo"
"git.acooldomain.co/server-manager/backend/models"
)
@ -133,6 +136,21 @@ func GetUserPassAuthDbHandler(config models.UserPassAuthConfig) (dbhandler.UserP
}
userPassAuthDbHandlers[key] = handler
ctx, cancel := context.WithTimeoutCause(context.Background(), 5*time.Second, errors.New("Timeout"))
defer cancel()
if config.InitialUser == nil {
return handler, nil
}
count, _ := handler.CountUsers(ctx)
if count == 0 {
log.Printf("Trying to create user %#v\n", config.InitialUser)
err := handler.CreateUser(ctx, config.InitialUser.Username, config.InitialUser.Password, models.Admin, config.InitialUser.Email, 10)
if err != nil {
log.Printf("Failed to create initial user %e\n", err)
}
}
return handler, nil
}

View File

@ -4,12 +4,12 @@ import (
"context"
"encoding/json"
"fmt"
"maps"
"net"
instancemanager "git.acooldomain.co/server-manager/backend/instancemanager"
"git.acooldomain.co/server-manager/backend/models"
"github.com/buildkite/shellwords"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
@ -22,11 +22,11 @@ import (
type InstanceManager struct {
instancemanager.InstanceManager
client client.Client
client *client.Client
config models.DockerInstanceManagerConfig
}
func (self *InstanceManager) containerList(ctx context.Context, labels ContainerLabels, all bool) ([]types.Container, error) {
func (self *InstanceManager) containerList(ctx context.Context, labels ContainerLabels, all bool) ([]container.Summary, error) {
filters, err := convertLabelsToFilter(labels)
if err != nil {
return nil, err
@ -61,12 +61,17 @@ func (self *InstanceManager) getVolume(ctx context.Context, serverId string) (*v
// General
// Read Only
func (self *InstanceManager) GetImage(ctx context.Context, imageId string) (*instancemanager.Image, error) {
imageInspect, _, err := self.client.ImageInspectWithRaw(ctx, imageId)
imageInspect, err := self.client.ImageInspect(ctx, imageId)
if err != nil {
return nil, err
}
if imageInspect.Config.Labels["type"] != "game" {
imageLabels, err := convertImageLabelsToStruct(imageInspect.Config.Labels)
if err != nil {
return nil, err
}
if imageLabels.Type != Game {
return nil, fmt.Errorf("Image not found")
}
@ -89,7 +94,7 @@ func (self *InstanceManager) ListImages(ctx context.Context) ([]instancemanager.
images := make([]instancemanager.Image, len(rawImages))
for i, rawImage := range rawImages {
imageInspect, _, err := self.client.ImageInspectWithRaw(ctx, rawImage.ID)
imageInspect, err := self.client.ImageInspect(ctx, rawImage.ID)
if err != nil {
return nil, err
}
@ -502,9 +507,7 @@ func (self *InstanceManager) StartFileBrowser(ctx context.Context, serverId stri
return nil, err
}
for key, value := range *containerConfig {
browserLabels[key] = value
}
maps.Copy(browserLabels, *containerConfig)
command := self.config.FileBrowser.Command
@ -579,6 +582,6 @@ func NewInstanceManager(config models.DockerInstanceManagerConfig) (*InstanceMan
return &InstanceManager{
config: config,
client: *apiClient,
client: apiClient,
}, nil
}

View File

@ -8,8 +8,9 @@ import (
instancemanager "git.acooldomain.co/server-manager/backend/instancemanager"
"git.acooldomain.co/server-manager/backend/models"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/go-connections/nat"
)
@ -54,15 +55,15 @@ func stringifyMap(m map[string]any) map[string]string {
return stringifiedMap
}
func convertContainerPortsToPorts(ports []types.Port) []models.Port {
func convertContainerPortsToPorts(ports []container.Port) []models.Port {
containerPorts := make([]models.Port, len(ports))
logger := log.Default()
for i, port := range ports {
var portProtocol models.PortProtocol
switch port.Type {
case "TCP":
case "tcp":
portProtocol = models.TCP
case "UDP":
case "udp":
portProtocol = models.UDP
default:
logger.Println(fmt.Sprintf("Unkown Port Protocol %s assuming TCP", port.Type))
@ -90,10 +91,11 @@ func convertImageStringToModelsImage(image string) models.Image {
}
}
func convertImageInspectToInstanceImage(image types.ImageInspect) instancemanager.Image {
func convertImageInspectToInstanceImage(image image.InspectResponse) instancemanager.Image {
modelsImage := convertImageStringToModelsImage(image.RepoTags[0])
ports := convertImagePortsToPorts(image.Config.ExposedPorts)
fmt.Printf("image: %#v\nconfig: %#v\nports: %#v\n", image, image.Config, ports)
return instancemanager.Image{
Registry: modelsImage.Registry,
@ -112,7 +114,7 @@ func convertContainerLabelsToStruct(labels map[string]string) (*ContainerLabels,
return nil, err
}
err = json.Unmarshal(rawLabels, &labels)
err = json.Unmarshal(rawLabels, &containerLabels)
if err != nil {
return nil, err
@ -129,7 +131,7 @@ func convertVolumeLabelsToStruct(labels map[string]string) (*VolumeLabels, error
return nil, err
}
err = json.Unmarshal(rawLabels, &labels)
err = json.Unmarshal(rawLabels, &volumeLabels)
if err != nil {
return nil, err
@ -146,7 +148,7 @@ func convertImageLabelsToStruct(labels map[string]string) (*ImageLabels, error)
return nil, err
}
err = json.Unmarshal(rawLabels, &labels)
err = json.Unmarshal(rawLabels, &imageLabels)
if err != nil {
return nil, err
@ -161,9 +163,9 @@ func convertImagePortsToPorts(rawPorts nat.PortSet) []instancemanager.Port {
portNumber := imagePort.Int()
var protocol models.PortProtocol
switch imagePort.Proto() {
case "TCP":
case "tcp":
protocol = models.TCP
case "UDP":
case "udp":
protocol = models.UDP
default:
log.Default().Println(fmt.Sprintf("Unknown port protocol %s using TCP", imagePort.Proto()))

View File

@ -1,5 +1,11 @@
package models
type InitialUserConfig struct {
Email string `yaml:"email"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type EmailConfig struct {
FromEmail string `yaml:"from_email"`
Username string `yaml:"username"`
@ -35,6 +41,7 @@ type UserPassAuthConfig struct {
Type DatabaseType `yaml:"type"`
Mongo *MongoDBConfig `yaml:"mongo"`
InviteTokenDatabase InviteTokenDatabaseConfig `yaml:"invite_token_database"`
InitialUser *InitialUserConfig `yaml:"initial_user"`
}
type AuthenticationConfig struct {

36
servers/auth_utils.go Normal file
View File

@ -0,0 +1,36 @@
package servers
import (
"git.acooldomain.co/server-manager/backend/auth"
"git.acooldomain.co/server-manager/backend/models"
"github.com/gin-gonic/gin"
)
func (con ServersApi) ServerAuthorized(permissions models.Permission) func(*gin.Context) {
return func(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims")
if !exists {
ctx.AbortWithStatus(403)
return
}
claims := claimsPointer.(*auth.AuthClaims)
serverId := ctx.Param("server_id")
if serverId == "" {
return
}
userPermissions, err := con.ServerAuthorization.GetPermissions(ctx, claims.Username, serverId)
if err != nil {
return
}
if userPermissions&permissions == permissions || userPermissions&models.Admin == models.Admin {
ctx.Set(auth.AuthorizedParam, true)
return
}
return
}
}

View File

@ -51,6 +51,6 @@ func LoadBrowsersGroup(group *gin.RouterGroup, config models.GlobalConfig) {
InstanceManager: instanceManager,
}
group.GET("", auth.AuthorizedTo(0), connection.GetBrowsers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Browse), connection.StopBrowser)
group.GET("", auth.AuthorizedTo(0), auth.AuthorizationEnforcer(), connection.GetBrowsers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Browse), auth.AuthorizationEnforcer(), connection.StopBrowser)
}

View File

@ -70,5 +70,5 @@ func LoadeImagesGroup(group *gin.RouterGroup, config models.GlobalConfig) {
InstanceManager: instanceManager,
}
group.GET("", auth.AuthorizedTo(0), connection.GetImages)
group.GET("", auth.AuthorizedTo(0), auth.AuthorizationEnforcer(), connection.GetImages)
}

View File

@ -64,37 +64,6 @@ type CreateServerRequest struct {
Nickname string `json:"Nickname"`
}
func (con ServersApi) ServerAuthorized(permissions models.Permission) func(*gin.Context) {
return func(ctx *gin.Context) {
claimsPointer, exists := ctx.Get("claims")
if !exists {
ctx.AbortWithStatus(403)
return
}
claims := claimsPointer.(*auth.AuthClaims)
serverId := ctx.Param("server_id")
if serverId == "" {
ctx.AbortWithStatus(403)
return
}
userPermissions, err := con.ServerAuthorization.GetPermissions(ctx, claims.Username, serverId)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if userPermissions&permissions == permissions || userPermissions&models.Admin == models.Admin {
return
}
ctx.AbortWithStatus(403)
return
}
}
func (con ServersApi) CreateServer(ctx *gin.Context) {
claims, exists := ctx.Get("claims")
if !exists {
@ -168,6 +137,11 @@ func (con ServersApi) StartServer(ctx *gin.Context) {
}
instanceServer, err := con.InstanceManager.GetServer(ctx, serverId)
if err != nil {
ctx.AbortWithError(500, err)
return
}
if instanceServer.Running {
ctx.Status(200)
return
@ -176,6 +150,7 @@ func (con ServersApi) StartServer(ctx *gin.Context) {
server, err := con.ServersDbHandler.GetServer(ctx, serverId)
if err != nil {
ctx.AbortWithError(500, err)
return
}
err = con.InstanceManager.StartServer(
@ -558,15 +533,15 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) {
InstanceManager: instanceManager,
}
group.POST("/:server_id/start", auth.AuthorizedTo(models.Start), connection.ServerAuthorized(models.Start), connection.StartServer)
group.POST("", auth.AuthorizedTo(models.Create), connection.CreateServer)
group.GET("", auth.AuthorizedTo(0), connection.GetServers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop), connection.ServerAuthorized(models.Stop), connection.StopServer)
group.DELETE("/:server_id", auth.AuthorizedTo(models.Delete), connection.ServerAuthorized(models.Delete), connection.DeleteServer)
group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), connection.RunCommand)
group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), connection.AttachServer)
group.PATCH("/:server_id", auth.AuthorizedTo(models.Admin), connection.ServerAuthorized(models.Admin), connection.UpdateServer)
group.POST("/:server_id/browse", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), connection.BrowseServer)
group.GET("/:server_id/permissions", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), connection.GetServerUserPermissions)
group.POST("/:server_id/permissions", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), connection.SetServerUserPermissions)
group.POST("/:server_id/start", auth.AuthorizedTo(models.Start), connection.ServerAuthorized(models.Start), auth.AuthorizationEnforcer(), connection.StartServer)
group.POST("", auth.AuthorizedTo(models.Create), auth.AuthorizationEnforcer(), connection.CreateServer)
group.GET("", connection.GetServers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop), connection.ServerAuthorized(models.Stop), auth.AuthorizationEnforcer(), connection.StopServer)
group.DELETE("/:server_id", auth.AuthorizedTo(models.Delete), connection.ServerAuthorized(models.Delete), auth.AuthorizationEnforcer(), connection.DeleteServer)
group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), auth.AuthorizationEnforcer(), connection.RunCommand)
group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand), connection.ServerAuthorized(models.RunCommand), auth.AuthorizationEnforcer(), connection.AttachServer)
group.PATCH("/:server_id", auth.AuthorizedTo(models.Admin), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.UpdateServer)
group.POST("/:server_id/browse", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.BrowseServer)
group.GET("/:server_id/permissions", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.GetServerUserPermissions)
group.POST("/:server_id/permissions", auth.AuthorizedTo(models.Browse), connection.ServerAuthorized(models.Admin), auth.AuthorizationEnforcer(), connection.SetServerUserPermissions)
}

View File

@ -122,9 +122,9 @@ func LoadGroup(group *gin.RouterGroup, config models.GlobalConfig) {
config: &config,
}
group.GET("", auth.AuthorizedTo(0), connection.GetUsers)
group.GET("/@me", auth.AuthorizedTo(0), connection.GetUser)
group.POST("", auth.AuthorizedTo(models.Admin), connection.InviteUser)
group.DELETE("/:user_id", auth.AuthorizedTo(models.Admin), connection.DeleteUser)
group.PATCH("/:user_id/permissions", auth.AuthorizedTo(models.Admin), connection.SetUserPermissions)
group.GET("", auth.AuthorizedTo(0), auth.AuthorizationEnforcer(), connection.GetUsers)
group.GET("/@me", auth.AuthorizedTo(0), auth.AuthorizationEnforcer(), connection.GetUser)
group.POST("", auth.AuthorizedTo(models.Admin), auth.AuthorizationEnforcer(), connection.InviteUser)
group.DELETE("/:user_id", auth.AuthorizedTo(models.Admin), auth.AuthorizationEnforcer(), connection.DeleteUser)
group.PATCH("/:user_id/permissions", auth.AuthorizedTo(models.Admin), auth.AuthorizationEnforcer(), connection.SetUserPermissions)
}