From 16b685063533d0c0fbf314eca6215a8e40c446af Mon Sep 17 00:00:00 2001 From: ACoolName Date: Tue, 14 May 2024 15:12:18 +0300 Subject: [PATCH] added more logic --- .vscode/launch.json | 6 +- auth/auth.go | 34 +++++------ servers/servers.go | 135 +++++++++++++++++++++++++++++++++++++++++--- users/users.go | 2 +- 4 files changed, 152 insertions(+), 25 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0f8103e..6e1e186 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,11 @@ "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}" + "program": "${workspaceFolder}", + "env": { + "HOST_IP": "127.0.0.1", + "UPNP_PATH": "test.upnp" + } } ] } \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go index f885728..d6b6e6d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -52,12 +52,12 @@ func hashPassword(password string) (string, error) { return string(bytes), err } -func AuthorizedTo(requiredPermissions models.Permission) gin.HandlerFunc { +func AuthorizedTo(requiredPermissions models.Permission, overwriters ...func(*gin.Context) bool) gin.HandlerFunc { return func(ctx *gin.Context) { - fmt.Println("Auth logic starts") authCookie, err := ctx.Request.Cookie("auth") if err != nil { ctx.AbortWithError(403, err) + return } token, err := jwt.ParseWithClaims(authCookie.Value, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) { @@ -71,12 +71,18 @@ func AuthorizedTo(requiredPermissions models.Permission) gin.HandlerFunc { }) if err != nil { ctx.AbortWithError(403, err) + return } - fmt.Println(token.Claims) if claims, ok := token.Claims.(*AuthClaims); ok && token.Valid { ctx.Set("claims", claims) if requiredPermissions&claims.Permissions != requiredPermissions && models.Admin&claims.Permissions != models.Admin { + for _, overwrite := range overwriters { + if overwrite(ctx) { + return + } + } ctx.AbortWithStatus(403) + return } } else { ctx.AbortWithStatus(500) @@ -118,22 +124,18 @@ func (con Connection) signIn(c *gin.Context) { if err != nil { c.AbortWithError(500, err) + return } var userItem models.User err = con.connection.Database("Backend").Collection("Users").FindOne(context.TODO(), bson.D{{Key: "Username", Value: request.Username}}).Decode(&userItem) if err != nil { c.AbortWithError(403, err) + return } - err = bcrypt.CompareHashAndPassword([]byte(userItem.HashedPass), []byte(request.Password)) - if err != nil { - println(err.Error()) - pass, err := hashPassword(request.Password) - if err != nil { - c.AbortWithError(403, err) - } - fmt.Printf("UserPass: %s\nDatabaseHash: %s\nHash %s\n", request.Password, userItem.HashedPass, pass) + if bcrypt.CompareHashAndPassword([]byte(userItem.HashedPass), []byte(request.Password)) != nil { c.AbortWithStatus(403) + return } token := TokenInfo{ @@ -144,6 +146,7 @@ func (con Connection) signIn(c *gin.Context) { signedToken, err := signToken(token) if err != nil { c.AbortWithError(500, err) + return } c.SetCookie("auth", signedToken, -1, "", "", false, false) @@ -155,6 +158,7 @@ func (con Connection) test(c *gin.Context) { if !exists { fmt.Println("No Claims") c.AbortWithStatus(403) + return } c.IndentedJSON(http.StatusOK, claims) } @@ -162,9 +166,7 @@ func (con Connection) test(c *gin.Context) { func LoadGroup(group *gin.RouterGroup, client *mongo.Client) { connection := Connection{connection: client} group.POST("/signin", connection.signIn) - group.Use(AuthorizedTo(models.Admin)) - { - group.POST("/signup", connection.signUp) - group.GET("/test", connection.test) - } + + group.POST("/signup", AuthorizedTo(models.Admin), connection.signUp) + group.GET("/test", AuthorizedTo(models.Admin), connection.test) } diff --git a/servers/servers.go b/servers/servers.go index 87fe68a..ec2b461 100644 --- a/servers/servers.go +++ b/servers/servers.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "os" "strings" + "time" "acooldomain.co/backend/auth" "acooldomain.co/backend/models" @@ -16,6 +18,7 @@ import ( "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" "github.com/gin-gonic/gin" v1 "github.com/opencontainers/image-spec/specs-go/v1" "go.mongodb.org/mongo-driver/bson" @@ -51,6 +54,22 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e return nil, err } json.Unmarshal(jsonData, &volumeLabels) + imageList, err := con.apiClient.ImageList(context.Background(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("reference", volumeLabels.ImageId))}) + if len(imageList) == 0 { + return nil, fmt.Errorf("ImageId %s does not exist", volumeLabels.ImageId) + } + imageSummery := imageList[0] + imageInspect, _, err := con.apiClient.ImageInspectWithRaw(context.Background(), imageSummery.ID) + if err != nil { + return nil, err + } + + var imagePorts []models.Port = make([]models.Port, len(imageInspect.Config.ExposedPorts)) + i := 0 + for imagePort := range imageInspect.Config.ExposedPorts { + imagePorts[i] = models.Port{Protocol: imagePort.Proto(), Number: imagePort.Int()} + i += 1 + } imageNameAndVersion := strings.Split(volumeLabels.ImageId, ":") @@ -78,6 +97,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e Image: models.ImageInfo{ Name: imageName, Version: imageVersion, + Ports: imagePorts, }, OwnerId: volumeLabels.OwnerId, On: state, @@ -126,16 +146,19 @@ func (con Connection) CreateServer(ctx *gin.Context) { claims, exists := ctx.Get("claims") if !exists { ctx.AbortWithStatus(500) + return } var request CreateServerRequest err := json.NewDecoder(ctx.Request.Body).Decode(&request) if err != nil { ctx.AbortWithError(400, err) + return } imageList, err := con.apiClient.ImageList(context.Background(), image.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("reference", request.ImageId))}) if err != nil { ctx.AbortWithError(400, err) + return } if len(imageList) == 0 { ctx.AbortWithStatusJSON(404, "imageNotFound") @@ -145,16 +168,17 @@ func (con Connection) CreateServer(ctx *gin.Context) { labels, err := convertLabelsToMap(VolumeLabels{OwnerId: claims.(*auth.AuthClaims).Username, ImageId: imageSummary.RepoTags[0], Type: "GAME"}) if err != nil { ctx.AbortWithError(400, err) + return } volumeResponse, err := con.apiClient.VolumeCreate(context.Background(), volume.CreateOptions{ Labels: labels, }) if err != nil { ctx.AbortWithError(500, err) + return } ctx.JSON(200, volumeResponse.Name) - } func (con Connection) StartServer(ctx *gin.Context) { @@ -162,6 +186,7 @@ func (con Connection) StartServer(ctx *gin.Context) { claims, exists := ctx.Get("claims") if !exists { ctx.AbortWithStatus(403) + return } // command := ctx.Param("command") @@ -170,6 +195,7 @@ func (con Connection) StartServer(ctx *gin.Context) { if err != nil { ctx.AbortWithError(500, err) + return } if serverInfo.On { @@ -187,6 +213,7 @@ func (con Connection) StartServer(ctx *gin.Context) { jsonString, err := json.Marshal(labels) if err != nil { ctx.AbortWithError(500, err) + return } jsonLabels := make(map[string]string) json.Unmarshal(jsonString, &jsonLabels) @@ -196,6 +223,18 @@ func (con Connection) StartServer(ctx *gin.Context) { image, _, err := con.apiClient.ImageInspectWithRaw(context.Background(), imageId) if err != nil { ctx.AbortWithError(500, err) + return + } + + var portMapping nat.PortMap = make(nat.PortMap) + + for _, port := range serverInfo.Image.Ports { + dockerPort, err := nat.NewPort(port.Protocol, fmt.Sprint(port.Number)) + if err != nil { + ctx.AbortWithError(500, err) + return + } + portMapping[dockerPort] = []nat.PortBinding{{HostIP: "0.0.0.0"}} } response, err := con.apiClient.ContainerCreate( @@ -212,8 +251,9 @@ func (con Connection) StartServer(ctx *gin.Context) { Labels: jsonLabels, }, &container.HostConfig{ - AutoRemove: true, - Mounts: []mount.Mount{{Source: serverInfo.Id, Target: image.Config.WorkingDir, Type: "volume"}}, + AutoRemove: true, + Mounts: []mount.Mount{{Source: serverInfo.Id, Target: image.Config.WorkingDir, Type: "volume"}}, + PortBindings: portMapping, }, &network.NetworkingConfig{}, &v1.Platform{}, @@ -221,9 +261,41 @@ func (con Connection) StartServer(ctx *gin.Context) { ) if err != nil { ctx.AbortWithError(500, err) + return } if err := con.apiClient.ContainerStart(ctx, response.ID, container.StartOptions{}); err != nil { ctx.AbortWithError(500, err) + return + } + UPNPPath, exists := os.LookupEnv("UPNP_PATH") + HostIP, hostIPexists := os.LookupEnv("HOST_IP") + if exists && hostIPexists { + time.Sleep(time.Millisecond * 100) + containerData, err := con.apiClient.ContainerInspect(context.Background(), response.ID) + if err != nil { + ctx.AbortWithError(500, err) + return + } + fo, err := os.OpenFile(UPNPPath, os.O_APPEND, os.ModeAppend) + if err != nil { + panic(err) + } + defer func() { + if err := fo.Close(); err != nil { + panic(err) + } + }() + + for containerPort, portBindings := range containerData.NetworkSettings.Ports { + for _, hostPort := range portBindings { + number, proto := hostPort.HostPort, containerPort.Proto() + _, err := fo.Write([]byte(fmt.Sprintf("%s|%s|%s|%s|%s\n", HostIP, number, "0.0.0.0", number, strings.ToUpper(proto)))) + if err != nil { + ctx.AbortWithError(500, err) + return + } + } + } } ctx.JSON(200, response.ID) } @@ -239,7 +311,6 @@ func (con Connection) GetServers(ctx *gin.Context) { ctx.AbortWithError(500, err) } var servers []models.ServerInfo - println("Found %d Containers", len(volumes.Volumes)) for _, volume := range volumes.Volumes { serverInfo, err := con.getServerInfo(*volume) if err != nil { @@ -252,7 +323,56 @@ func (con Connection) GetServers(ctx *gin.Context) { ctx.AbortWithError(500, err) } ctx.JSON(200, servers) +} +func (con Connection) StopServer(ctx *gin.Context) { + serverId := ctx.Param("server_id") + containersList, err := con.apiClient.ContainerList(context.Background(), container.ListOptions{ + Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME")), + }) + if err != nil { + ctx.AbortWithError(500, err) + return + } + if len(containersList) == 0 { + ctx.Status(200) + return + } + + for _, containerData := range containersList { + con.apiClient.ContainerStop(context.Background(), containerData.ID, container.StopOptions{}) + } + ctx.Status(200) +} + +func (con Connection) serverAuthorized(permissions models.Permission) func(*gin.Context) bool { + return func(ctx *gin.Context) bool { + claims, exists := ctx.Get("claims") + if !exists { + return false + } + + server_id := ctx.Param("server_id") + if server_id == "" { + return false + } + + var serverData models.ServerData + + con.connection.Database("Backend").Collection("Servers").FindOne(context.Background(), bson.D{{Key: "ServerId", Value: server_id}}).Decode(&serverData) + + if serverData.OwnerId == claims.(*auth.AuthClaims).Username { + return true + } + + userPermissions := serverData.UserPermissions[claims.(*auth.AuthClaims).Username] + + if userPermissions&permissions == permissions || userPermissions&models.Admin == models.Admin { + return true + } + + return false + } } func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) { @@ -263,7 +383,8 @@ func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) { defer apiClient.Close() connection := Connection{connection: mongo_client, apiClient: apiClient} - group.Use(auth.AuthorizedTo(models.Start)).POST("/:server_id/start", connection.StartServer) - group.Use(auth.AuthorizedTo(models.Create)).POST("/", connection.CreateServer) - group.GET("/", connection.GetServers) + 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) } diff --git a/users/users.go b/users/users.go index 49ea985..3f8a249 100644 --- a/users/users.go +++ b/users/users.go @@ -33,5 +33,5 @@ func (con Connection) GetUsers(c *gin.Context) { func LoadGroup(group *gin.RouterGroup, client *mongo.Client) { connection := Connection{connection: client} - group.Use(auth.AuthorizedTo(0)).GET("/", connection.GetUsers) + group.GET("/", auth.AuthorizedTo(0), connection.GetUsers) }