refactored

This commit is contained in:
2025-03-18 23:27:27 +02:00
parent 64f59ea232
commit 6c1f34c682
45 changed files with 398 additions and 413 deletions

1
dbhandler/db_handler.go Normal file
View File

@@ -0,0 +1 @@
package dbhandler

3
dbhandler/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.acooldomain.co/server-manager/backend/dbhandler
go 1.22.0

19
dbhandler/mongo/go.mod Normal file
View File

@@ -0,0 +1,19 @@
module git.acooldomain.co/server-manager/backend/dbhandler/mongo
go 1.24.1
require go.mongodb.org/mongo-driver v1.14.0
require (
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.15.0 // indirect
)

13
dbhandler/mongo/go.sum Normal file
View File

@@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=

192
dbhandler/mongo/servers.go Normal file
View File

@@ -0,0 +1,192 @@
package mongo
import (
"context"
"fmt"
"time"
"git.acooldomain.co/server-manager/backend/dbhandler"
"git.acooldomain.co/server-manager/backend/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Image struct {
Registry string `bson:"registry"`
Tag string `bson:"tag"`
}
type Port struct {
PublicPort uint16 `bson:"public_port"`
ContainerPort uint16 `bson:"container_port"`
Protocol models.PortProtocol `bson:"protocol"`
}
type Server struct {
Id string `bson:"id"`
Owner string `bson:"owner"`
Image Image `bson:"image"`
Nickname string `bson:"nickname"`
Command string `bson:"command"`
Ports []Port `bson:"ports"`
}
func convertToDbServer(server dbhandler.Server) Server {
ports := make([]Port, len(server.Ports))
for i, port := range server.Ports {
ports[i] = Port{
PublicPort: port.PublicPort,
ContainerPort: port.ContainerPort,
Protocol: port.Protocol,
}
}
return Server{
Id: server.Id,
Owner: server.Owner,
Image: Image{
Registry: server.Image.Registry,
Tag: server.Image.Tag,
},
Nickname: server.Nickname,
Command: server.Command,
Ports: ports,
}
}
func convertToResponseServer(server Server) dbhandler.Server {
ports := make([]models.Port, len(server.Ports))
for i, port := range server.Ports {
ports[i] = models.Port{
PublicPort: port.PublicPort,
ContainerPort: port.ContainerPort,
Protocol: port.Protocol,
}
}
return dbhandler.Server{
Id: server.Id,
Owner: server.Owner,
Image: &models.Image{
Registry: server.Image.Registry,
Tag: server.Image.Tag,
},
Nickname: server.Nickname,
Command: server.Command,
Ports: ports,
}
}
type ServersDbHandler struct {
dbhandler.ServersDbHandler
collection *mongo.Collection
}
func (self *ServersDbHandler) ListServers(ctx context.Context) ([]dbhandler.Server, error) {
var servers []Server
cursor, err := self.collection.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
err = cursor.All(ctx, &servers)
if err != nil {
return nil, err
}
dbServers := make([]dbhandler.Server, len(servers))
for i, server := range servers {
dbServers[i] = convertToResponseServer(server)
}
return dbServers, nil
}
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)
if err != nil {
return nil, err
}
responseServer := convertToResponseServer(server)
return &responseServer, nil
}
func (self *ServersDbHandler) CreateServer(ctx context.Context, server dbhandler.Server) error {
dbServer := convertToDbServer(server)
_, err := self.collection.InsertOne(ctx, &dbServer)
return err
}
func (self *ServersDbHandler) DeleteServer(ctx context.Context, serverId string) error {
_, err := self.collection.DeleteOne(ctx, bson.M{
"server_id": serverId,
})
return err
}
func (self *ServersDbHandler) UpdateServer(ctx context.Context, serverId string, updateParams dbhandler.ServerUpdateRequest) error {
updateServerRequest := bson.M{}
if updateParams.Owner != "" {
updateServerRequest["owner"] = updateParams.Owner
}
if updateParams.Image != nil {
updateServerRequest["image"] = bson.M{
"registry": updateParams.Image.Registry,
"tag": updateParams.Image.Tag,
}
}
if updateParams.Ports != nil {
ports := make([]bson.M, len(updateParams.Ports))
for i, port := range updateParams.Ports {
ports[i] = bson.M{
"number": port.PublicPort,
"protocol": port.Protocol,
}
}
}
if updateParams.Nickname != "" {
updateServerRequest["nickname"] = updateParams.Nickname
}
if updateParams.Command != "" {
updateServerRequest["command"] = updateParams.Command
}
_, err := self.collection.UpdateOne(ctx, bson.M{"server_id": serverId}, bson.M{"$set": updateServerRequest})
return err
}
func NewServersDbHandler(config models.MongoDBConfig) (*ServersDbHandler, error) {
clientOptions := options.Client().ApplyURI(config.Url).SetAuth(options.Credential{
Username: config.Username,
Password: config.Password,
})
ctx, cancel := context.WithTimeoutCause(context.Background(), 30*time.Second, fmt.Errorf("Timeout"))
defer cancel()
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return &ServersDbHandler{
collection: client.Database(config.Database).Collection(config.Collection),
}, nil
}

View File

@@ -0,0 +1,161 @@
package mongo
import (
"context"
"fmt"
"time"
"git.acooldomain.co/server-manager/backend/dbhandler"
"git.acooldomain.co/server-manager/backend/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ServerPermissions struct {
Username string `bson:"username"`
ServerId string `bson:"server_id"`
Permissions models.Permission `bson:"permissions"`
}
type ServersAuthorizationDbHandler struct {
dbhandler.ServersAuthorizationDbHandler
collection *mongo.Collection
}
func (self *ServersAuthorizationDbHandler) RemoveUser(ctx context.Context, username string) error {
_, err := self.collection.DeleteMany(
ctx,
bson.M{
"username": username,
},
)
return err
}
func (self *ServersAuthorizationDbHandler) RemoveServer(ctx context.Context, serverId string) error {
_, err := self.collection.DeleteMany(
ctx,
bson.M{
"server_id": serverId,
},
)
return err
}
func (self *ServersAuthorizationDbHandler) AddPermissions(ctx context.Context, username string, serverId string, permissions models.Permission) error {
var serverPermissions ServerPermissions
err := self.collection.FindOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
).Decode(&serverPermissions)
if err != nil {
return err
}
newPermissions := serverPermissions.Permissions | permissions
_, err = self.collection.UpdateOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
bson.M{"$set": bson.M{
"permissions": newPermissions,
},
},
)
return err
}
func (self *ServersAuthorizationDbHandler) RemovePermissions(ctx context.Context, username string, serverId string, permissions models.Permission) error {
var serverPermissions ServerPermissions
err := self.collection.FindOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
).Decode(&serverPermissions)
if err != nil {
return err
}
newPermissions := serverPermissions.Permissions | permissions ^ permissions
_, err = self.collection.UpdateOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
bson.M{"$set": bson.M{
"permissions": newPermissions,
},
},
)
return err
}
func (self *ServersAuthorizationDbHandler) SetPermissions(ctx context.Context, username string, serverId string, permissions models.Permission) error {
_, err := self.collection.UpdateOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
bson.M{"$set": bson.M{
"permissions": permissions,
},
},
)
return err
}
func (self *ServersAuthorizationDbHandler) GetPermissions(ctx context.Context, username string, serverId string) (models.Permission, error) {
var serverPermissions ServerPermissions
err := self.collection.FindOne(
ctx,
bson.M{
"username": username,
"server_id": serverId,
},
).Decode(&serverPermissions)
if err != nil {
return 0, err
}
return serverPermissions.Permissions, nil
}
func NewServersAuthorizationHandler(config models.MongoDBConfig) (*ServersAuthorizationDbHandler, error) {
clientOptions := options.Client().ApplyURI(config.Url).SetAuth(options.Credential{
Username: config.Username,
Password: config.Password,
})
ctx, cancel := context.WithTimeoutCause(context.Background(), 30*time.Second, fmt.Errorf("Timeout"))
defer cancel()
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return &ServersAuthorizationDbHandler{
collection: client.Database(config.Database).Collection(config.Collection),
}, nil
}

View File

@@ -0,0 +1,216 @@
package mongo
import (
"context"
"fmt"
"time"
"git.acooldomain.co/server-manager/backend/dbhandler"
"git.acooldomain.co/server-manager/backend/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type AuthUser struct {
Username string `json:"username"`
Nickname string `json:"nickname"`
HashedPassword string `json:"hashed_password"`
Permissions models.Permission `json:"permissions"`
MaxOwnedSevers uint `json:"max_owned_severs"`
Email string `json:"email"`
}
type UserPassAuthenticationDbHandler struct {
dbhandler.UserPassAuthanticationDbHandler
collection *mongo.Collection
}
func (self *UserPassAuthenticationDbHandler) ListUsers(ctx context.Context) ([]models.User, error) {
cursor, err := self.collection.Find(ctx, nil)
if err != nil {
return nil, err
}
var authUsers []AuthUser
cursor.All(ctx, &authUsers)
modelUsers := make([]models.User, len(authUsers))
for i, authUser := range authUsers {
modelUsers[i] = models.User{
Username: authUser.Username,
Nickname: authUser.Nickname,
Email: authUser.Email,
MaxOwnedServers: authUser.MaxOwnedSevers,
Permissions: authUser.Permissions,
}
}
return modelUsers, nil
}
func (self *UserPassAuthenticationDbHandler) AuthenticateUser(ctx context.Context, username string, password string) (*models.User, error) {
var user AuthUser
err := self.collection.FindOne(ctx, bson.M{"username": username}).Decode(&user)
if err != nil {
return nil, err
}
hashedPassword, err := dbhandler.HashPassword(password)
if err != nil {
return nil, err
}
if user.HashedPassword != hashedPassword {
return nil, fmt.Errorf("Incorrect Password")
}
return &models.User{
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
MaxOwnedServers: user.MaxOwnedSevers,
Permissions: user.Permissions,
}, nil
}
func (self *UserPassAuthenticationDbHandler) CreateUser(
ctx context.Context,
username string,
password string,
permissions models.Permission,
email string,
maxOwnedServers uint,
) error {
hashedPassword, err := dbhandler.HashPassword(password)
if err != nil {
return err
}
_, err = self.collection.InsertOne(ctx, &AuthUser{
Username: username,
HashedPassword: hashedPassword,
Permissions: permissions,
Email: email,
MaxOwnedSevers: maxOwnedServers,
})
return err
}
func (self *UserPassAuthenticationDbHandler) RemoveUser(ctx context.Context, username string) error {
_, err := self.collection.DeleteOne(
ctx,
bson.M{
"username": username,
},
)
return err
}
func (self *UserPassAuthenticationDbHandler) SetPermissions(
ctx context.Context,
username string,
permissions models.Permission,
) error {
_, err := self.collection.UpdateOne(
ctx,
bson.M{
"username": username,
},
bson.M{
"$set": bson.M{
"permissions": permissions,
},
},
)
return err
}
func NewUserPassAuthHandler(config models.MongoDBConfig) (*UserPassAuthenticationDbHandler, error) {
clientOptions := options.Client().ApplyURI(config.Url).SetAuth(options.Credential{
Username: config.Username,
Password: config.Password,
})
ctx, cancel := context.WithTimeoutCause(context.Background(), 30*time.Second, fmt.Errorf("Timeout"))
defer cancel()
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return &UserPassAuthenticationDbHandler{
collection: client.Database(config.Database).Collection(config.Collection),
}, nil
}
type InviteToken struct {
Email string `json:"email"`
Token string `json:"token"`
Permissions models.Permission `json:"permissions"`
}
type InviteTokenDbHandler struct {
dbhandler.InviteTokenDbHandler
collection *mongo.Collection
}
func (self *ServersDbHandler) SaveInviteToken(ctx context.Context, email string, permissions models.Permission) (string, error) {
token := uuid.NewString()
_, err := self.collection.InsertOne(ctx, &InviteToken{
Permissions: permissions,
Email: email,
Token: token,
})
if err != nil {
return "", err
}
return token, nil
}
func (self *ServersDbHandler) GetInviteToken(ctx context.Context, token string) (*dbhandler.InviteToken, error) {
var inviteToken InviteToken
err := self.collection.FindOne(ctx, bson.M{"token": token}).Decode(&inviteToken)
if err != nil {
return nil, err
}
return &dbhandler.InviteToken{
Email: inviteToken.Email,
Permissions: inviteToken.Permissions,
Token: inviteToken.Token,
}, nil
}
func NewInviteTokenDbHandler(config models.MongoDBConfig) (*InviteTokenDbHandler, error) {
clientOptions := options.Client().ApplyURI(config.Url).SetAuth(options.Credential{
Username: config.Username,
Password: config.Password,
})
ctx, cancel := context.WithTimeoutCause(context.Background(), 30*time.Second, fmt.Errorf("Timeout"))
defer cancel()
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return &InviteTokenDbHandler{
collection: client.Database(config.Database).Collection(config.Collection),
}, nil
}

78
dbhandler/oidc_auth.go Normal file
View File

@@ -0,0 +1,78 @@
package dbhandler
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"git.acooldomain.co/server-manager/backend/models"
"github.com/coreos/go-oidc"
"golang.org/x/oauth2"
)
type CallbackRequest struct {
Code string `json:"code"`
}
type OidcClaims struct {
Email string
Profile string
Permissions models.Permission
}
type OidcAuthenticationDbHandler struct {
provider *oidc.Provider
oauth2Config *oauth2.Config
}
func GenerateOidcState() string {
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
log.Fatal(err)
}
return base64.URLEncoding.EncodeToString(b)
}
func (self *OidcAuthenticationDbHandler) AuthenticateUser(ctx context.Context, request CallbackRequest) (models.Permission, error) {
token, err := self.oauth2Config.Exchange(ctx, request.Code)
if err != nil {
return 0, err
}
verifier := self.provider.Verifier(&oidc.Config{ClientID: self.oauth2Config.ClientID})
idToken, ok := token.Extra("id_token").(string)
if !ok {
return 0, fmt.Errorf("Failed to convert id_token to string")
}
tokenObj, err := verifier.Verify(ctx, idToken)
var claims OidcClaims
if err := tokenObj.Claims(&claims); err != nil {
return 0, err
}
return claims.Permissions, nil
}
func NewOidcAuthenticationDbHamdler(config models.OidcAuthConfig) (*OidcAuthenticationDbHandler, error) {
provider, err := oidc.NewProvider(context.Background(), config.IssuerUrl)
if err != nil {
return nil, err
}
return &OidcAuthenticationDbHandler{
provider: provider,
oauth2Config: &oauth2.Config{
ClientID: config.ClientId,
ClientSecret: config.ClientSecret,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "email", "name", "profile"},
},
}, nil
}

32
dbhandler/servers.go Normal file
View File

@@ -0,0 +1,32 @@
package dbhandler
import (
"context"
"git.acooldomain.co/server-manager/backend/models"
)
type Server struct {
Id string
Owner string
Image *models.Image
Nickname string
Command string
Ports []models.Port
}
type ServerUpdateRequest struct {
Owner string
Image *models.Image
Nickname string
Command string
Ports []models.Port
}
type ServersDbHandler interface {
ListServers(ctx context.Context) ([]Server, error)
GetServer(ctx context.Context, serverId string) (*Server, error)
CreateServer(ctx context.Context, server Server) error
DeleteServer(ctx context.Context, serverId string) error
UpdateServer(ctx context.Context, serverId string, updateParams ServerUpdateRequest) error
}

View File

@@ -0,0 +1,16 @@
package dbhandler
import (
"context"
"git.acooldomain.co/server-manager/backend/models"
)
type ServersAuthorizationDbHandler interface {
AddPermissions(ctx context.Context, username string, server_id string, permissions models.Permission) error
RemovePermissions(ctx context.Context, string, server_id string, permissions models.Permission) error
SetPermissions(ctx context.Context, username string, server_id string, permissions models.Permission) error
GetPermissions(ctx context.Context, username string, server_id string) (models.Permission, error)
RemoveUser(ctx context.Context, username string) error
RemoveServer(ctx context.Context, server_id string) error
}

View File

@@ -0,0 +1,42 @@
package dbhandler
import (
"context"
"git.acooldomain.co/server-manager/backend/models"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
type InviteToken struct {
Email string
Permissions models.Permission
Token string
}
type UserSignupRequest struct {
Token InviteToken
Username string
Password string
}
type UserPassAuthanticationDbHandler interface {
// Read Only
AuthenticateUser(ctx context.Context, username string, password string) (*models.User, error)
ListUsers(ctx context.Context) ([]models.User, error)
// Write
CreateUser(ctx context.Context, username string, password string, permissions models.Permission, email string, maxOwnedServers uint) error
RemoveUser(ctx context.Context, username string) error
SetPermissions(ctx context.Context, username string, permissions models.Permission) error
SetPassword(ctx context.Context, username string, password string) error
}
type InviteTokenDbHandler interface {
SaveInviteToken(ctx context.Context, email string, permissions models.Permission) (string, error)
GetInviteToken(ctx context.Context, token string) (*InviteToken, error)
}