moved stuff around

This commit is contained in:
ACoolName 2024-05-18 04:10:28 +03:00
parent 7d3051d4cb
commit 9834ab88c8
13 changed files with 351 additions and 61 deletions

3
.vscode/launch.json vendored
View File

@ -12,7 +12,8 @@
"program": "${workspaceFolder}", "program": "${workspaceFolder}",
"env": { "env": {
"HOST_IP": "127.0.0.1", "HOST_IP": "127.0.0.1",
"UPNP_PATH": "test.upnp" "UPNP_PATH": "test.upnp",
"CONFIG_PATH": "config.json"
} }
} }
] ]

View File

@ -7,8 +7,6 @@ import (
"net/http" "net/http"
"time" "time"
// "acoolname.co/backend/models"
"acooldomain.co/backend/models" "acooldomain.co/backend/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
@ -17,7 +15,8 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
var hmacSampleSecret []byte var secret []byte
var method string
type Connection struct { type Connection struct {
connection *mongo.Client connection *mongo.Client
@ -33,9 +32,15 @@ type AuthClaims struct {
TokenInfo TokenInfo
} }
type InviteToken struct {
Email string `bson:"Email"`
Permissions []models.Permission `bson:"Permissions"`
Token string `bson:"Token"`
}
func signToken(token TokenInfo) (string, error) { func signToken(token TokenInfo) (string, error) {
t := jwt.New(jwt.GetSigningMethod("HS512")) t := jwt.New(jwt.GetSigningMethod(method))
t.Claims = &AuthClaims{ t.Claims = &AuthClaims{
&jwt.StandardClaims{ &jwt.StandardClaims{
@ -44,7 +49,7 @@ func signToken(token TokenInfo) (string, error) {
token, token,
} }
return t.SignedString(hmacSampleSecret) return t.SignedString(secret)
} }
func hashPassword(password string) (string, error) { func hashPassword(password string) (string, error) {
@ -67,7 +72,7 @@ func AuthorizedTo(requiredPermissions models.Permission, overwriters ...func(*gi
} }
// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
return hmacSampleSecret, nil return secret, nil
}) })
if err != nil { if err != nil {
ctx.AbortWithError(403, err) ctx.AbortWithError(403, err)
@ -90,14 +95,15 @@ func AuthorizedTo(requiredPermissions models.Permission, overwriters ...func(*gi
} }
} }
type SignUpRequest struct {
Token string
Username string
Password string
}
func (con Connection) signUp(c *gin.Context) { func (con Connection) signUp(c *gin.Context) {
var token TokenInfo var token TokenInfo
type SignUpRequest struct {
token string
username string
password string
}
err := json.NewDecoder(c.Request.Body).Decode(&token) err := json.NewDecoder(c.Request.Body).Decode(&token)
if err != nil { if err != nil {
c.AbortWithError(500, err) c.AbortWithError(500, err)
@ -163,10 +169,13 @@ func (con Connection) test(c *gin.Context) {
c.IndentedJSON(http.StatusOK, claims) c.IndentedJSON(http.StatusOK, claims)
} }
func LoadGroup(group *gin.RouterGroup, client *mongo.Client) { func LoadGroup(group *gin.RouterGroup, client *mongo.Client, config models.GlobalConfig) {
connection := Connection{connection: client} connection := Connection{connection: client}
group.POST("/signin", connection.signIn)
secret = []byte(config.Key)
method = config.Algorithm
group.POST("/signin", connection.signIn)
group.POST("/signup", AuthorizedTo(models.Admin), connection.signUp) group.POST("/signup", AuthorizedTo(models.Admin), connection.signUp)
group.GET("/test", AuthorizedTo(models.Admin), connection.test) group.GET("/test", AuthorizedTo(models.Admin), connection.test)
} }

1
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect

2
go.sum
View File

@ -31,6 +31,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

View File

@ -5,6 +5,7 @@ use (
./auth ./auth
./users ./users
./db_handler ./db_handler
./mail
./models ./models
./servers ./servers
) )

3
mail/go.mod Normal file
View File

@ -0,0 +1,3 @@
module acooldomain.co/backend/mail
go 1.22.0

87
mail/mail.go Normal file
View File

@ -0,0 +1,87 @@
package mail
import (
"crypto/tls"
"fmt"
"net/mail"
"net/smtp"
"acooldomain.co/backend/models"
)
const EMAIL_SERVER_ENV_VAR = "EMAIL_SERVER"
const FROM_EMAIL_ENV_VAR = "FROM_EMAIL"
var auth *smtp.Auth
var mailConfig *models.EmailConfig
func InitializeClient(config models.EmailConfig) error {
simpleAuth := smtp.PlainAuth("", config.Username, config.Password, config.Server)
auth = &simpleAuth
mailConfig = &config
return nil
}
func SendMail(
recipient string,
subject string,
content string,
) error {
if auth == nil {
return fmt.Errorf("mail not initialized")
}
if mailConfig == nil {
return fmt.Errorf("mail not initialized")
}
from := mail.Address{Name: "", Address: mailConfig.FromEmail}
to := mail.Address{Name: "", Address: recipient}
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + content
conn, err := tls.Dial("tcp", mailConfig.Server+":465", &tls.Config{ServerName: mailConfig.Server})
if err != nil {
return err
}
client, err := smtp.NewClient(conn, mailConfig.Server)
if err != nil {
return err
}
if err = client.Auth(*auth); err != nil {
return err
}
if err = client.Mail(mailConfig.FromEmail); err != nil {
return err
}
if err = client.Rcpt(recipient); err != nil {
return err
}
writer, err := client.Data()
if err != nil {
return err
}
_, err = writer.Write([]byte(message))
if err != nil {
return err
}
if err = writer.Close(); err != nil {
return err
}
return client.Quit()
}

35
main.go
View File

@ -2,18 +2,40 @@ package main
import ( import (
"context" "context"
"encoding/json"
"os"
"acooldomain.co/backend/auth" "acooldomain.co/backend/auth"
"acooldomain.co/backend/dbhandler" "acooldomain.co/backend/dbhandler"
"acooldomain.co/backend/mail"
"acooldomain.co/backend/models"
"acooldomain.co/backend/servers" "acooldomain.co/backend/servers"
"acooldomain.co/backend/users" "acooldomain.co/backend/users"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
const CONFIG_PATH_ENV_VAR = "CONFIG_PATH"
const MONGO_URL_ENV_VAR = "MONGO_URL"
func main() { func main() {
router := gin.Default() router := gin.Default()
file, err := os.Open(os.Getenv(CONFIG_PATH_ENV_VAR))
if err != nil {
panic(err)
}
client, err := dbhandler.Connect("mongodb://localhost:27017") var config models.GlobalConfig
err = json.NewDecoder(file).Decode(&config)
if err != nil {
panic(err)
}
mongo_url := os.Getenv(MONGO_URL_ENV_VAR)
if mongo_url == "" {
mongo_url = "mongodb://localhost:27017"
}
client, err := dbhandler.Connect(mongo_url)
defer func() { defer func() {
if err = client.Disconnect(context.TODO()); err != nil { if err = client.Disconnect(context.TODO()); err != nil {
panic(err) panic(err)
@ -23,9 +45,12 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
users.LoadGroup(router.Group("/users"), client) mail.InitializeClient(config.Email)
auth.LoadGroup(router.Group("/auth"), client)
servers.LoadGroup(router.Group("/servers"), client)
router.Run("localhost:8080") users.LoadGroup(router.Group("/users"), client, config)
auth.LoadGroup(router.Group("/auth"), client, config)
servers.LoadGroup(router.Group("/servers"), client, config)
servers.LoadBrowsersGroup(router.Group("/browsers"), client, config)
router.Run("127.0.0.1:8080")
} }

14
models/config.go Normal file
View File

@ -0,0 +1,14 @@
package models
type EmailConfig struct {
FromEmail string
Username string
Password string
Server string
}
type GlobalConfig struct {
Email EmailConfig
Key string
Algorithm string
}

View File

@ -25,6 +25,7 @@ type FileBrowserInfo struct {
Id string Id string
OwnerId string OwnerId string
ConnectedTo ServerInfo ConnectedTo ServerInfo
Url string
} }
type ServerData struct { type ServerData struct {

116
servers/browsers.go Normal file
View File

@ -0,0 +1,116 @@
package servers
import (
"context"
"encoding/json"
"fmt"
"acooldomain.co/backend/auth"
"acooldomain.co/backend/models"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/mongo"
)
func (con Connection) getBrowserInfo(volume volume.Volume) (*models.FileBrowserInfo, error) {
serverInfo, err := con.getServerInfo(volume)
if err != nil {
return nil, err
}
containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
All: true,
Filters: filters.NewArgs(filters.Arg("label", "type=FILE_BROWSER"), filters.Arg("label", fmt.Sprintf("volume_id=%s", volume.Name))),
})
if err != nil || len(containers) == 0 {
return nil, nil
}
container := containers[0]
jsonData, err := json.Marshal(container.Labels)
if err != nil {
return nil, err
}
var browserInfo ContainerLabels
err = json.Unmarshal(jsonData, &browserInfo)
if err != nil {
return nil, err
}
return &models.FileBrowserInfo{
Id: container.ID,
OwnerId: browserInfo.OwnerId,
ConnectedTo: *serverInfo,
Url: container.ID[:12] + "." + DOMAIN,
}, nil
}
func (con Connection) GetBrowsers(ctx *gin.Context) {
volumes, err := con.dockerClient.VolumeList(
context.TODO(),
volume.ListOptions{
Filters: filters.NewArgs(filters.Arg("label", "type=GAME")),
},
)
if err != nil {
ctx.AbortWithError(500, err)
}
var servers []models.FileBrowserInfo
for _, volume := range volumes.Volumes {
browserInfo, err := con.getBrowserInfo(*volume)
if err != nil {
ctx.AbortWithError(500, err)
}
if browserInfo == nil {
continue
}
servers = append(servers, *browserInfo)
}
if err != nil {
ctx.AbortWithError(500, err)
}
ctx.JSON(200, servers)
}
func (con Connection) StopBrowser(ctx *gin.Context) {
serverId := ctx.Param("server_id")
containersList, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=FILE_BROWSER")),
})
if err != nil {
ctx.AbortWithError(500, err)
return
}
if len(containersList) == 0 {
ctx.Status(200)
return
}
for _, containerData := range containersList {
con.dockerClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{})
}
ctx.Status(200)
}
func LoadBrowsersGroup(group *gin.RouterGroup, mongo_client *mongo.Client, config models.GlobalConfig) {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
defer apiClient.Close()
connection := Connection{databaseConnection: mongo_client, dockerClient: apiClient}
group.GET("/", auth.AuthorizedTo(0), connection.GetBrowsers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Browse, connection.serverAuthorized(models.Browse)), connection.StopBrowser)
}

View File

@ -591,36 +591,6 @@ func (con Connection) AttachServer(ctx *gin.Context) {
} }
} }
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.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", 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
}
}
type UpdateServerRequest struct { type UpdateServerRequest struct {
DefaultPorts []models.Port `json:"DefaultPorts"` DefaultPorts []models.Port `json:"DefaultPorts"`
DefaultCommand string `json:"DefaultCommand"` DefaultCommand string `json:"DefaultCommand"`
@ -669,17 +639,21 @@ func (con Connection) BrowseServer(ctx *gin.Context) {
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
} }
labelId := serverInfo.Id[:12]
browserLabels := make(map[string]string) browserLabels := make(map[string]string)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", serverInfo.Id)] = fmt.Sprintf("%s.%s", "browsers", DOMAIN) browserLabels["traefik.enable"] = "true"
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", serverInfo.Id)] = fmt.Sprintf("*.%s.%s", "browsers", DOMAIN) browserLabels[fmt.Sprintf("traefik.http.routers.%s.rule", labelId)] = fmt.Sprintf("Host(`%s.{service_type}.{DOMAIN}`)", labelId)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", serverInfo.Id)] = "myresolver" browserLabels[fmt.Sprintf("traefik.http.routers.%s.entrypoints", labelId)] = "websecure"
browserLabels[fmt.Sprintf("traefik.http.routers.%s.middlewares", labelId)] = "games@docker"
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", labelId)] = fmt.Sprintf("%s.%s", "browsers", DOMAIN)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", labelId)] = fmt.Sprintf("*.%s.%s", "browsers", DOMAIN)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", labelId)] = "myresolver"
containerLabels := ContainerLabels{ containerLabels := ContainerLabels{
OwnerId: claims.(*auth.AuthClaims).Username, OwnerId: claims.(*auth.AuthClaims).Username,
ImageId: FILE_BROWSER_IMAGE, ImageId: FILE_BROWSER_IMAGE,
VolumeId: serverInfo.Id, VolumeId: serverInfo.Id,
Type: "FILE-BROWSER", Type: "FILE_BROWSER",
} }
jsonLabels, err := json.Marshal(containerLabels) jsonLabels, err := json.Marshal(containerLabels)
if err != nil { if err != nil {
@ -706,7 +680,7 @@ func (con Connection) BrowseServer(ctx *gin.Context) {
} }
if len(images) == 0 { if len(images) == 0 {
ctx.AbortWithError(500, fmt.Errorf("Image %s no longer exists", imageRef)) ctx.AbortWithError(500, fmt.Errorf("image %s no longer exists", imageRef))
return return
} }
@ -716,7 +690,6 @@ func (con Connection) BrowseServer(ctx *gin.Context) {
Cmd: command, Cmd: command,
Image: FILE_BROWSER_IMAGE, Image: FILE_BROWSER_IMAGE,
Labels: browserLabels, Labels: browserLabels,
ExposedPorts: nat.PortSet{"80/tcp": struct{}{}},
}, },
&container.HostConfig{ &container.HostConfig{
Mounts: []mount.Mount{{Source: serverInfo.Id, Target: "/tmp/data", Type: "volume"}}, Mounts: []mount.Mount{{Source: serverInfo.Id, Target: "/tmp/data", Type: "volume"}},
@ -745,7 +718,37 @@ func (con Connection) BrowseServer(ctx *gin.Context) {
ctx.JSON(200, "OK") ctx.JSON(200, "OK")
} }
func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) { 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.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", 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, config models.GlobalConfig) {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -2,11 +2,14 @@ package users
import ( import (
"context" "context"
"encoding/json"
"net/http" "net/http"
"acooldomain.co/backend/auth" "acooldomain.co/backend/auth"
"acooldomain.co/backend/mail"
"acooldomain.co/backend/models" "acooldomain.co/backend/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
) )
@ -31,7 +34,31 @@ func (con Connection) GetUsers(c *gin.Context) {
c.IndentedJSON(http.StatusOK, response) c.IndentedJSON(http.StatusOK, response)
} }
func LoadGroup(group *gin.RouterGroup, client *mongo.Client) { type InviteUser struct {
Email string `json:"Email"`
Permissions []models.Permission `json:"Permissions"`
}
func (con Connection) InviteUser(c *gin.Context) {
var request InviteUser
json.NewDecoder(c.Request.Body).Decode(&request)
token := uuid.NewString()
err := mail.SendMail(request.Email, "You've been invited to join", "please open this link https://games.acooldomain.co/signup?token="+token)
if err != nil {
c.AbortWithError(500, err)
return
}
con.connection.Database("Backend").Collection("Tokens").InsertOne(context.TODO(), auth.InviteToken{
Email: request.Email,
Permissions: request.Permissions,
Token: token,
})
c.JSON(200, "OK")
}
func LoadGroup(group *gin.RouterGroup, client *mongo.Client, config models.GlobalConfig) {
connection := Connection{connection: client} connection := Connection{connection: client}
group.GET("/", auth.AuthorizedTo(0), connection.GetUsers) group.GET("/", auth.AuthorizedTo(0), connection.GetUsers)
group.POST("/", auth.AuthorizedTo(models.Admin), connection.InviteUser)
} }