From f862606991938bcead0e3e85bcc6daddfd8823f2 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Fri, 17 Jan 2025 20:44:31 -0300 Subject: [PATCH 1/9] Refactor authentication and authorization modules Removed redundant auth and security logic, consolidating the user authentication flow. Replaced middleware with a streamlined handler-based approach, reducing dependencies and improving maintainability. Updated type definitions and imports to align with the new structure. --- cmd/generator/api_secret/main.go | 7 +- cmd/server/main.go | 20 ++- cmd/stream/main.go | 10 +- go.mod | 14 +- go.sum | 34 ++-- pkg/auth/access_secret.go | 155 ------------------ pkg/auth/access_validation.go | 88 ---------- pkg/auth/data.go | 36 ---- .../repository/user/users_repository_impl.go | 2 +- pkg/domain/service/security/api_secret_jwt.go | 26 --- pkg/domain/service/security/def.go | 39 ----- pkg/domain/service/security/encryptor.go | 13 -- pkg/domain/service/security/jwt.go | 116 ------------- .../service/{user => user_service}/service.go | 17 +- pkg/handlers/handlers.go | 3 +- pkg/handlers/peer/get_available.go | 5 +- pkg/handlers/peer/handlers.go | 12 +- pkg/handlers/peer/stream.go | 6 +- pkg/handlers/request/context.go | 37 ++++- pkg/handlers/user/create.go | 12 +- pkg/handlers/user/handlers.go | 31 ++-- pkg/handlers/user/login.go | 11 +- pkg/handlers/user/update.go | 20 +-- .../user/user_handler/access_handler.go | 26 +++ pkg/models/peer.go | 2 +- pkg/models/user.go | 2 +- 26 files changed, 175 insertions(+), 569 deletions(-) delete mode 100644 pkg/auth/access_secret.go delete mode 100644 pkg/auth/access_validation.go delete mode 100644 pkg/auth/data.go delete mode 100644 pkg/domain/service/security/api_secret_jwt.go delete mode 100644 pkg/domain/service/security/def.go delete mode 100644 pkg/domain/service/security/encryptor.go delete mode 100644 pkg/domain/service/security/jwt.go rename pkg/domain/service/{user => user_service}/service.go (70%) create mode 100644 pkg/handlers/user/user_handler/access_handler.go diff --git a/cmd/generator/api_secret/main.go b/cmd/generator/api_secret/main.go index 52c183a..d3d5dbb 100644 --- a/cmd/generator/api_secret/main.go +++ b/cmd/generator/api_secret/main.go @@ -7,9 +7,10 @@ import ( "encoding/pem" "flag" "github.com/atotto/clipboard" + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/security" "github.com/softwareplace/wireguard-api/pkg/domain/db" "github.com/softwareplace/wireguard-api/pkg/domain/repository/api_secret" - "github.com/softwareplace/wireguard-api/pkg/domain/service/security" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/env" "log" @@ -97,7 +98,7 @@ func main() { Bytes: publicKeyBytes, }) - encryptedKey, err := security.GetApiSecurityService().Encrypt(string(publicKeyPEM)) + encryptedKey, err := security.GetApiSecurityService[api_context.DefaultContext](appEnv.ApiSecretAuthorization).Encrypt(string(publicKeyPEM)) if err != nil { log.Fatalf("Failed to sec public key: %s", err) @@ -125,7 +126,7 @@ func main() { Key: *id, } - apiSecretJWT, err := security.GetApiSecurityService().GenerateApiSecretJWT(apiJWTInfo) + apiSecretJWT, err := security.GetApiSecurityService[api_context.DefaultContext](appEnv.ApiSecretAuthorization).GenerateApiSecretJWT(apiJWTInfo) if err != nil { return diff --git a/cmd/server/main.go b/cmd/server/main.go index ead7ed2..1e71c5a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,23 +2,25 @@ package main import ( "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/auth" "github.com/softwareplace/wireguard-api/pkg/domain/db" "github.com/softwareplace/wireguard-api/pkg/domain/service/peer" - "github.com/softwareplace/wireguard-api/pkg/domain/service/user" + "github.com/softwareplace/wireguard-api/pkg/domain/service/user_service" "github.com/softwareplace/wireguard-api/pkg/handlers" "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "github.com/softwareplace/wireguard-api/pkg/handlers/user/user_handler" ) func main() { db.InitMongoDB() - api := server.New() - api.Router().Use(request.ContextBuilder) - handler := auth.NewApiSecurityHandler() - api.Router().Use(handler.Middleware) - api.Router().Use(auth.AccessValidation) - handlers.Init(api) + api := server.New[*request.ApiContext]() + api.Use(request.ContextBuilder, "API/CONTEXT/INITIALIZER") + + service := user_service.GetService() + userAuthenticationUserHandler := user_handler.GetAuthenticationUserHandler(&service) + api.Use(userAuthenticationUserHandler.Handler, "MIDDLEWARE/AUTHENTICATION_USER") + + handlers.Init(&api) peer.GetService().Load() - user.GetService().Init() + service.Init() api.StartServer() } diff --git a/cmd/stream/main.go b/cmd/stream/main.go index 867ad62..a4deb21 100644 --- a/cmd/stream/main.go +++ b/cmd/stream/main.go @@ -2,10 +2,10 @@ package main import ( "fmt" + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/request" "github.com/softwareplace/wireguard-api/cmd/stream/parse" "github.com/softwareplace/wireguard-api/cmd/stream/spec" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/http_api" "go/types" "log" "net/http" @@ -47,11 +47,11 @@ func main() { //fmt.Println("Dump as JSON:") //fmt.Println(string(jsonDump)) - api := http_api.NewApi(types.Nil{}) - config := http_api.Config(streamEnv.Server). + api := request.NewApi(types.Nil{}) + config := request.Build(streamEnv.Server). WithPath("peers/stream"). WithHeader("Authorization", streamEnv.Authorization). - WithHeader(request.XApiKey, streamEnv.ApiKey). + WithHeader(api_context.XApiKey, streamEnv.ApiKey). WithBody(dump). WithExpectedStatusCode(http.StatusCreated) diff --git a/go.mod b/go.mod index 6ab922f..9feccb8 100644 --- a/go.mod +++ b/go.mod @@ -6,25 +6,25 @@ toolchain go1.23.4 require ( github.com/atotto/clipboard v0.1.4 - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/google/uuid v1.6.0 - github.com/gorilla/mux v1.8.1 - go.mongodb.org/mongo-driver v1.17.1 - golang.org/x/crypto v0.31.0 + github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd + go.mongodb.org/mongo-driver v1.17.2 + golang.org/x/crypto v0.32.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/montanaflynn/stats v0.7.1 // indirect - github.com/softwareplace/http-utils v0.0.0-20250115004038-6ed463f52d1a // 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-20240726163527-a2c0da244d78 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 5bc9289..9e96caf 100644 --- a/go.sum +++ b/go.sum @@ -16,14 +16,16 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/softwareplace/http-utils v0.0.0-20250115001316-f7f5d1f4f930 h1:umXPNgmmtm7nxaguVOHvWMutLs72fc3dDIPgd17xThg= -github.com/softwareplace/http-utils v0.0.0-20250115001316-f7f5d1f4f930/go.mod h1:qbgKuvJXcx4I6HEOmi1yFSfX8MpygEAj7s5ggceNSUk= -github.com/softwareplace/http-utils v0.0.0-20250115002306-419dc927f49f h1:f1FN/OEJ0tp2CcBKxWOkt6nlNfw4danBXThv2P2Yido= -github.com/softwareplace/http-utils v0.0.0-20250115002306-419dc927f49f/go.mod h1:qbgKuvJXcx4I6HEOmi1yFSfX8MpygEAj7s5ggceNSUk= -github.com/softwareplace/http-utils v0.0.0-20250115003340-50c4c89e24bd h1:Nl6ggz2YYIceKD1wi6MzdQUtVxQFGdRFIQQCI5MbYgA= -github.com/softwareplace/http-utils v0.0.0-20250115003340-50c4c89e24bd/go.mod h1:qbgKuvJXcx4I6HEOmi1yFSfX8MpygEAj7s5ggceNSUk= -github.com/softwareplace/http-utils v0.0.0-20250115004038-6ed463f52d1a h1:UIF8CFvYjmBBzHcIOdNXfimJVdvP1uuuY1zRRbwCh9k= -github.com/softwareplace/http-utils v0.0.0-20250115004038-6ed463f52d1a/go.mod h1:qbgKuvJXcx4I6HEOmi1yFSfX8MpygEAj7s5ggceNSUk= +github.com/softwareplace/http-utils v0.0.0-20250116230044-ed510c97fc93 h1:ozum1P9qUdOMrItHokPNVHbOHZNObM2s2fYBWo3bxe0= +github.com/softwareplace/http-utils v0.0.0-20250116230044-ed510c97fc93/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= +github.com/softwareplace/http-utils v0.0.0-20250116232827-e496aa73762e h1:PngEYXx329HwewdTbn/g0iKcozTlvR0Y61a+1Q2fYtE= +github.com/softwareplace/http-utils v0.0.0-20250116232827-e496aa73762e/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= +github.com/softwareplace/http-utils v0.0.0-20250117012334-addd8fbd1220 h1:4CYLGGScSzkWEnV5rxbgGmEWpYcPwgpXb958LnXp3+g= +github.com/softwareplace/http-utils v0.0.0-20250117012334-addd8fbd1220/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= +github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6 h1:uxMnbpDzjwnTeJNT9W5NZzkbITRG1CzS5BbkLMD39k0= +github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= +github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd h1:NTHsh5xr4DlIGwaNrEuq7vREYryGPZol7qFYsQgPl8s= +github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -33,12 +35,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= -go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -52,12 +54,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/pkg/auth/access_secret.go b/pkg/auth/access_secret.go deleted file mode 100644 index d6a7d85..0000000 --- a/pkg/auth/access_secret.go +++ /dev/null @@ -1,155 +0,0 @@ -package auth - -import ( - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/domain/repository/api_secret" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/utils/env" - "log" - "net/http" - "os" -) - -var ( - // apiSecret is expected to be an environment variable, adjust as needed - apiSecret any // Replace with logic to fetch from environment variables or similar - appEnv = env.AppEnv() -) - -type ApiSecurityHandler interface { - InitAPISecretKey() - Middleware(next http.Handler) http.Handler - ValidatePublicKey(ctx server.ApiRequestContext) error -} - -type apiSecurityHandlerImpl struct{} - -func NewApiSecurityHandler() ApiSecurityHandler { - return &apiSecurityHandlerImpl{} -} - -func (a *apiSecurityHandlerImpl) Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Validate the public key - ctx := server.Of(w, r, "MIDDLEWARE/API_SECRET") - - if err := a.ValidatePublicKey(ctx); err != nil { - ctx.Error("You are not allowed to access this resource", http.StatusUnauthorized) - return - } - - ctx.Next(next) - }) -} - -// InitAPISecretKey initializes and validates the apiSecret environment variable. -// If apiSecret is provided, it ensures the private key from the specified path can be loaded. -// The application will crash if the private key cannot be loaded. -func (a *apiSecurityHandlerImpl) InitAPISecretKey() { - secretKey := appEnv.ApiSecretKey - // Load private key from the provided secretKey file path - privateKeyData, err := os.ReadFile(secretKey) - if err != nil { - log.Fatalf("Failed to read private key file: %s", err.Error()) - } - - // Decode PEM block from the private key data - block, _ := pem.Decode(privateKeyData) - if block == nil || block.Type != "PRIVATE KEY" { - log.Fatalf("Failed to decode private key PEM block") - } - - // Parse the private key using ParsePKCS8PrivateKey - privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - log.Fatalf("Failed to parse private key: %s", err.Error()) - } - apiSecret = privateKey - - switch key := apiSecret.(type) { - case *ecdsa.PrivateKey: - log.Println("Loaded ECDSA private key successfully") - case *rsa.PrivateKey: - log.Println("Loaded RSA private key successfully") - default: - log.Fatalf("Unsupported private key type: %T", key) - } -} - -// ValidatePublicKey validates a given public key (in Base64 format) against the private key (apiSecret). -// This is performed only if mustValidatePublicKey is true. -func (a *apiSecurityHandlerImpl) ValidatePublicKey(ctx server.ApiRequestContext) error { - // Decode the Base64-encoded public key - claims, err := apiSecurityService.JWTClaims(ctx) - - if err != nil { - return err - } - apiContext := ctx.RequestData.(request.ApiContext) - - apiContext.SetApiKeyClaims(claims) - - id, err := apiSecurityService.Decrypt(claims["key"].(string)) - - if err != nil { - return err - } - - apiAccessSecret, err := api_secret.GetRepository().GetById(id) - apiContext.SetApiKeyId(id) - if err != nil { - return err - } - - // Decode the PEM-encoded public key - decryptKey, err := apiSecurityService.Decrypt(apiAccessSecret.Key) - if err != nil { - return err - } - block, _ := pem.Decode([]byte(decryptKey)) - if block == nil || block.Type != "PUBLIC KEY" { - return fmt.Errorf("failed to decode PEM public key") - } - - // Parse the public key - parsedPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return fmt.Errorf("failed to parse public key: %w", err) - } - - switch privateKey := apiSecret.(type) { - case *ecdsa.PrivateKey: - // Ensure the type of the public key matches ECDSA - publicKey, ok := parsedPublicKey.(*ecdsa.PublicKey) - if !ok { - return fmt.Errorf("invalid public key type, expected ECDSA") - } - - // Validate if the public key corresponds to the private key - privateKeyPubKey := &privateKey.PublicKey - if publicKey.X.Cmp(privateKeyPubKey.X) != 0 || publicKey.Y.Cmp(privateKeyPubKey.Y) != 0 { - return fmt.Errorf("public key does not match the private key") - } - case *rsa.PrivateKey: - // Ensure the type of the public key matches RSA - publicKey, ok := parsedPublicKey.(*rsa.PublicKey) - if !ok { - return fmt.Errorf("invalid public key type, expected RSA") - } - - // Validate if the public key corresponds to the private key - privateKeyPubKey := &privateKey.PublicKey - if publicKey.E != privateKeyPubKey.E || publicKey.N.Cmp(privateKeyPubKey.N) != 0 { - return fmt.Errorf("public key does not match the private key") - } - default: - return fmt.Errorf("unsupported private key type: %T", privateKey) - } - - return nil -} diff --git a/pkg/auth/access_validation.go b/pkg/auth/access_validation.go deleted file mode 100644 index 397c7ef..0000000 --- a/pkg/auth/access_validation.go +++ /dev/null @@ -1,88 +0,0 @@ -package auth - -import ( - "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/models" - "log" - "net/http" -) - -func AccessValidation(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - openPathLock.RLock() - defer openPathLock.RUnlock() - matchFound := false - for _, path := range openPath { - if path == r.Method+"::"+r.URL.Path { - matchFound = true - break - } - } - - ctx := server.Of(w, r, "MIDDLEWARE/ACCESS_VALIDATION") - - if !matchFound { - _, success := apiSecurityService.Validation(ctx, _nextValidation) - if !success { - return - } - - if !hasResourceAccess(ctx) { - return - } - } - - ctx.Next(next) - }) -} - -func hasResourceAccess(ctx server.ApiRequestContext) bool { - apiContext := ctx.RequestData.(request.ApiContext) - - userRoles, err := apiContext.GetRoles() - - if err != nil { - ctx.Error("You are not allowed to access this resource", http.StatusUnauthorized) - return false - } - - accessRoles, hasRoles := GetRolesForPath(ctx.Request) - - if !hasRoles { - ctx.Error("You are not allowed to access this resource", http.StatusUnauthorized) - return false - } - - hasAccess := false - - for _, userRole := range userRoles { - for _, accessRole := range accessRoles { - if userRole == accessRole { - hasAccess = true - break - } - } - if hasAccess { - break - } - } - - if !hasAccess { - ctx.Error("You are not allowed to access this resource", http.StatusUnauthorized) - return false - } - return true -} - -func _nextValidation(ctx server.ApiRequestContext) (*models.User, bool) { - apiContext := ctx.RequestData.(request.ApiContext) - - userData, err := usersRepo.FindUserBySalt(apiContext.AccessId) - if err != nil { - log.Printf("Failed to valiaded user %v access: %v", ctx, err) - return nil, false - } - apiContext.SetUser(userData) - return userData, true -} diff --git a/pkg/auth/data.go b/pkg/auth/data.go deleted file mode 100644 index ac5cb49..0000000 --- a/pkg/auth/data.go +++ /dev/null @@ -1,36 +0,0 @@ -package auth - -import ( - "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" - "github.com/softwareplace/wireguard-api/pkg/domain/service/security" - "net/http" - "sync" -) - -var ( - roles = make(map[string][]string) - openPath []string - openPathLock sync.RWMutex - apiSecurityService = security.GetApiSecurityService() - usersRepo = user.Repository() -) - -func AddOpenPath(path string) { - openPathLock.Lock() - defer openPathLock.Unlock() - openPath = append(openPath, path) -} - -func AddRoles(path string, requiredRoles ...string) { - if len(requiredRoles) > 0 { - roles[path] = requiredRoles - } -} - -func GetRolesForPath(r *http.Request) ([]string, bool) { - path := r.Method + "::" + r.URL.Path - if roles[path] != nil { - return roles[path], true - } - return nil, false -} diff --git a/pkg/domain/repository/user/users_repository_impl.go b/pkg/domain/repository/user/users_repository_impl.go index d5a596f..f54c2d4 100644 --- a/pkg/domain/repository/user/users_repository_impl.go +++ b/pkg/domain/repository/user/users_repository_impl.go @@ -39,7 +39,7 @@ func (r *usersRepositoryImpl) FindUserBySalt(salt string) (*models.User, error) }).Decode(¤tUser) if err != nil { - log.Printf("Error finding user by salt: %v", err) + log.Printf("Error finding user_service by salt: %v", err) return nil, err } return ¤tUser, nil diff --git a/pkg/domain/service/security/api_secret_jwt.go b/pkg/domain/service/security/api_secret_jwt.go deleted file mode 100644 index ae1ede7..0000000 --- a/pkg/domain/service/security/api_secret_jwt.go +++ /dev/null @@ -1,26 +0,0 @@ -package security - -import ( - "github.com/dgrijalva/jwt-go" - "time" -) - -// GenerateApiSecretJWT creates a JWT token with the username and role -func (a *apiSecurityServiceImpl) GenerateApiSecretJWT(jwtInfo ApiJWTInfo) (string, error) { - secret := a.Secret() - - encryptedKey, err := a.Encrypt(jwtInfo.Key) - if err != nil { - return "", err - } - - duration := time.Hour * jwtInfo.Expiration - expiration := time.Now().Add(duration).Unix() - claims := jwt.MapClaims{ - "client": jwtInfo.Client, - "key": encryptedKey, - "exp": expiration, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(secret) -} diff --git a/pkg/domain/service/security/def.go b/pkg/domain/service/security/def.go deleted file mode 100644 index c341d06..0000000 --- a/pkg/domain/service/security/def.go +++ /dev/null @@ -1,39 +0,0 @@ -package security - -import ( - "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/models" - "time" -) - -type ApiSecurityService interface { - Secret() []byte - GenerateApiSecretJWT(jwtInfo ApiJWTInfo) (string, error) - ExtractJWTClaims(requestContext server.ApiRequestContext) bool - JWTClaims(ctx server.ApiRequestContext) (map[string]interface{}, error) - GenerateJWT(user models.User) (map[string]interface{}, error) - Encrypt(key string) (string, error) - Decrypt(encrypted string) (string, error) - Validation( - ctx server.ApiRequestContext, - next func(ctx server.ApiRequestContext) (*models.User, bool), - ) (*models.User, bool) -} - -type ApiJWTInfo struct { - Client string - Key string - // Expiration in hours - Expiration time.Duration // -} - -type apiSecurityServiceImpl struct{} - -var ( - instance = &apiSecurityServiceImpl{} -) - -func GetApiSecurityService() ApiSecurityService { - instance.Secret() - return instance -} diff --git a/pkg/domain/service/security/encryptor.go b/pkg/domain/service/security/encryptor.go deleted file mode 100644 index b5f062e..0000000 --- a/pkg/domain/service/security/encryptor.go +++ /dev/null @@ -1,13 +0,0 @@ -package security - -import "github.com/softwareplace/wireguard-api/pkg/utils/sec" - -func (a *apiSecurityServiceImpl) Encrypt(value string) (string, error) { - secret := a.Secret() - return sec.Encrypt(value, secret) -} - -func (a *apiSecurityServiceImpl) Decrypt(encrypted string) (string, error) { - secret := a.Secret() - return sec.Decrypt(encrypted, secret) -} diff --git a/pkg/domain/service/security/jwt.go b/pkg/domain/service/security/jwt.go deleted file mode 100644 index af82c3d..0000000 --- a/pkg/domain/service/security/jwt.go +++ /dev/null @@ -1,116 +0,0 @@ -package security - -import ( - "fmt" - "github.com/dgrijalva/jwt-go" - "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/models" - envUtils "github.com/softwareplace/wireguard-api/pkg/utils/env" - "log" - "net/http" - "strconv" - "time" -) - -func (a *apiSecurityServiceImpl) Validation( - ctx server.ApiRequestContext, - next func(requestContext server.ApiRequestContext, - ) (*models.User, bool)) (*models.User, bool) { - success := a.ExtractJWTClaims(ctx) - - if !success { - return nil, success - } - - user, success := next(ctx) - - if !success { - ctx.Error("Authorization failed", http.StatusForbidden) - return nil, success - } - accessUserContext := ctx.RequestData.(request.ApiContext) - - accessUserContext.SetUser(user) - return user, success -} - -func (a *apiSecurityServiceImpl) ExtractJWTClaims(ctx server.ApiRequestContext) bool { - apiContext := ctx.RequestData.(request.ApiContext) - - token, err := jwt.Parse(ctx.Authorization, func(token *jwt.Token) (interface{}, error) { - return a.Secret(), nil - }) - - if err != nil { - log.Printf("JWT/PARSE: Authorization failed: %v", err) - ctx.Error("Authorization failed", http.StatusForbidden) - return false - } - - if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { - apiContext.SetAuthorizationClaims(claims) - - requester, err := a.Decrypt(claims["request"].(string)) - - if err != nil { - log.Printf("JWT/CLAIMS_EXTRACT: Authorization failed: %v", err) - ctx.Error("Authorization failed", http.StatusForbidden) - return false - } - - apiContext.SetAccessId(requester) - - return true - } - - log.Printf("JWT/CLAIMS_EXTRACT: failed with error_handler: %v", err) - ctx.Error("Authorization failed", http.StatusForbidden) - return false -} - -func (a *apiSecurityServiceImpl) JWTClaims(ctx server.ApiRequestContext) (map[string]interface{}, error) { - token, err := jwt.Parse(ctx.ApiKey, func(token *jwt.Token) (interface{}, error) { - return a.Secret(), nil - }) - - if err != nil { - return nil, err - } - - if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { - return claims, nil - } - - return nil, fmt.Errorf("failed to extract jwt claims") -} - -func (a *apiSecurityServiceImpl) Secret() []byte { - secret := envUtils.AppEnv().ApiSecretAuthorization - return []byte(secret) -} - -// GenerateJWT creates a JWT token with the username and role -func (a *apiSecurityServiceImpl) GenerateJWT(user models.User) (map[string]interface{}, error) { - duration := time.Minute * 15 - expiration := time.Now().Add(duration).Unix() - requestBy, err := a.Encrypt(user.Salt) - - var encryptedRoles []string - for _, role := range user.Roles { - encryptedRole, err := a.Encrypt(role) - if err != nil { - return nil, err - } - encryptedRoles = append(encryptedRoles, encryptedRole) - } - - claims := jwt.MapClaims{ - "request": requestBy, - "scope": encryptedRoles, - "exp": expiration, - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signedToken, err := token.SignedString(a.Secret()) - return map[string]interface{}{"token": signedToken, "expires": strconv.FormatInt(expiration, 10)}, err -} diff --git a/pkg/domain/service/user/service.go b/pkg/domain/service/user_service/service.go similarity index 70% rename from pkg/domain/service/user/service.go rename to pkg/domain/service/user_service/service.go index 07491b4..0b2799c 100644 --- a/pkg/domain/service/user/service.go +++ b/pkg/domain/service/user_service/service.go @@ -1,17 +1,21 @@ -package user +package user_service import ( + "github.com/softwareplace/http-utils/api_context" repo "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/env" "github.com/softwareplace/wireguard-api/pkg/utils/file" "github.com/softwareplace/wireguard-api/pkg/utils/sec" "github.com/softwareplace/wireguard-api/pkg/utils/validator" "log" + "net/http" ) type Service interface { Init() + LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string } type serviceImpl struct { @@ -19,6 +23,17 @@ type serviceImpl struct { repository repo.UsersRepository } +func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string { + user, err := s.repository.FindUserBySalt(ctx.RequestData.AccessId) + if err != nil { + log.Printf("[%s]:: error finding user_service: %v", ctx.GetSessionId(), err) + ctx.Error("Error finding user_service in the database", http.StatusInternalServerError) + return nil + } + ctx.RequestData.User = user + return user.Roles +} + func GetService() Service { return &serviceImpl{ appEnv: env.AppEnv(), diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index 057c763..c6ae1a2 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -3,10 +3,11 @@ package handlers import ( "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/handlers/peer" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/handlers/user" ) -func Init(api server.ApiRouterHandler) { +func Init(api *server.ApiRouterHandler[*request.ApiContext]) { user.Init(api) peer.Init(api) } diff --git a/pkg/handlers/peer/get_available.go b/pkg/handlers/peer/get_available.go index cae501b..6d5d3bb 100644 --- a/pkg/handlers/peer/get_available.go +++ b/pkg/handlers/peer/get_available.go @@ -1,12 +1,13 @@ package peer import ( - "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "log" "net/http" ) -func (h *handlerImpl) GetAvailablePeer(ctx *server.ApiRequestContext) { +func (h *handlerImpl) GetAvailablePeer(ctx *api_context.ApiRequestContext[*request.ApiContext]) { peer, err, notFound := h.Service().GetAvailablePeer() if notFound { log.Printf("[%s]:: no peer available: %v", ctx.GetSessionId(), err) diff --git a/pkg/handlers/peer/handlers.go b/pkg/handlers/peer/handlers.go index d42c933..116b31f 100644 --- a/pkg/handlers/peer/handlers.go +++ b/pkg/handlers/peer/handlers.go @@ -1,13 +1,15 @@ package peer import ( + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/service/peer" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" ) type Handler interface { - GetAvailablePeer(ctx *server.ApiRequestContext) - Stream(ctx *server.ApiRequestContext) + GetAvailablePeer(ctx *api_context.ApiRequestContext[*request.ApiContext]) + Stream(ctx *api_context.ApiRequestContext[*request.ApiContext]) Service() peer.Service } @@ -21,8 +23,8 @@ func (h *handlerImpl) Service() peer.Service { return peer.GetService() } -func Init(api server.ApiRouterHandler) { +func Init(api *server.ApiRouterHandler[*request.ApiContext]) { handler := GetHandler() - api.Get(handler.GetAvailablePeer, "peers", "resource:peers:get:peer") - api.Post(handler.Stream, "peers/stream", "resource:peers:stream:peers") + (*api).Get(handler.GetAvailablePeer, "peers", "resource:peers:get:peer") + (*api).Post(handler.Stream, "peers/stream", "resource:peers:stream:peers") } diff --git a/pkg/handlers/peer/stream.go b/pkg/handlers/peer/stream.go index 24c5166..58c6b8d 100644 --- a/pkg/handlers/peer/stream.go +++ b/pkg/handlers/peer/stream.go @@ -1,17 +1,19 @@ package peer import ( + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" "log" "net/http" ) -func (h *handlerImpl) Stream(ctx *server.ApiRequestContext) { +func (h *handlerImpl) Stream(ctx *api_context.ApiRequestContext[*request.ApiContext]) { server.GetRequestBody(ctx, []models.Peer{}, h.save, server.FailedToLoadBody) } -func (h *handlerImpl) save(ctx *server.ApiRequestContext, peers []models.Peer) { +func (h *handlerImpl) save(ctx *api_context.ApiRequestContext[*request.ApiContext], peers []models.Peer) { err := h.Service().Stream(peers) if err != nil { log.Printf("[%s]:: error saving peers: %v", ctx.GetSessionId(), err) diff --git a/pkg/handlers/request/context.go b/pkg/handlers/request/context.go index 8e5e9e9..87f38b5 100644 --- a/pkg/handlers/request/context.go +++ b/pkg/handlers/request/context.go @@ -2,9 +2,8 @@ package request import ( "fmt" - "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/wireguard-api/pkg/models" - "net/http" ) type ApiContext struct { @@ -15,12 +14,24 @@ type ApiContext struct { ApiKeyClaims map[string]interface{} } -func ContextBuilder(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := server.Of(w, r, "MIDDLEWARE/CONTEXT_BUILDER") - ctx.RequestData = &ApiContext{} - ctx.Next(next) - }) +func (ctx *ApiContext) Data(data api_context.ApiContextData) { + apiCtx := data.(*ApiContext) + ctx.User = apiCtx.User +} + +func (ctx *ApiContext) Salt() string { + if ctx.User != nil { + return ctx.User.Salt + + } + return "N/A" +} + +func (ctx *ApiContext) Roles() []string { + if ctx.User != nil { + return ctx.User.Roles + } + return []string{} } func (ctx *ApiContext) GetAccessApiKeyId() string { @@ -46,7 +57,7 @@ func (ctx *ApiContext) GetRoles() (roles []string, err error) { if user != nil && len(user.Roles) > 0 { return user.Roles, nil } - return nil, fmt.Errorf("user roles not found") + return nil, fmt.Errorf("user_service roles not found") } func (ctx *ApiContext) SetUser(user *models.User) { @@ -68,3 +79,11 @@ func (ctx *ApiContext) SetApiKeyId(apiKeyId string) { func (ctx *ApiContext) SetAccessId(accessId string) { ctx.AccessId = accessId } + +func (ctx *ApiContext) SetRoles(roles []string) { +} + +func ContextBuilder(ctx *api_context.ApiRequestContext[*ApiContext]) bool { + ctx.RequestData = &ApiContext{} + return true +} diff --git a/pkg/handlers/user/create.go b/pkg/handlers/user/create.go index 91c0ddd..d6e609b 100644 --- a/pkg/handlers/user/create.go +++ b/pkg/handlers/user/create.go @@ -1,7 +1,9 @@ package user import ( + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/sec" "github.com/softwareplace/wireguard-api/pkg/utils/validator" @@ -9,11 +11,11 @@ import ( "net/http" ) -func (h *handlerImpl) CreateUser(ctx *server.ApiRequestContext) { +func (h *handlerImpl) CreateUser(ctx *api_context.ApiRequestContext[*request.ApiContext]) { server.GetRequestBody(ctx, models.User{}, h.validateUserFields, server.FailedToLoadBody) } -func (h *handlerImpl) validateUserFields(ctx *server.ApiRequestContext, user models.User) { +func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*request.ApiContext], user models.User) { if err := validator.ValidateUserFields(user); err != nil { log.Printf("[%s]:: validation failed with error: %v", ctx.GetSessionId(), err) ctx.Error(err.Error(), http.StatusBadRequest) @@ -41,11 +43,11 @@ func (h *handlerImpl) validateUserFields(ctx *server.ApiRequestContext, user mod user.Status = "ACTIVE" if err := h.UsersRepository().Save(user); err != nil { - log.Printf("[%s]:: error saving user to the database: %v", ctx.GetSessionId(), err) - ctx.Error("Error saving user to the database", http.StatusInternalServerError) + log.Printf("[%s]:: error saving user_service to the database: %v", ctx.GetSessionId(), err) + ctx.Error("Error saving user_service to the database", http.StatusInternalServerError) return } - log.Printf("[%s]:: user created successfully", ctx.GetSessionId()) + log.Printf("[%s]:: user_service created successfully", ctx.GetSessionId()) ctx.Created(map[string]interface{}{"message": "User created successfully"}) } diff --git a/pkg/handlers/user/handlers.go b/pkg/handlers/user/handlers.go index f2f48e1..12ad291 100644 --- a/pkg/handlers/user/handlers.go +++ b/pkg/handlers/user/handlers.go @@ -1,35 +1,38 @@ package user import ( + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/security" "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" - "github.com/softwareplace/wireguard-api/pkg/domain/service/security" - "net/http" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "github.com/softwareplace/wireguard-api/pkg/utils/env" ) type Handler interface { UsersRepository() user.UsersRepository - Login(w http.ResponseWriter, r *http.Request) - CreateUser(w http.ResponseWriter, r *http.Request) - UpdateUser(w http.ResponseWriter, r *http.Request) + Login(ctx *api_context.ApiRequestContext[*request.ApiContext]) + CreateUser(ctx *api_context.ApiRequestContext[*request.ApiContext]) + UpdateUser(ctx *api_context.ApiRequestContext[*request.ApiContext]) + JWTService() security.ApiSecurityService[*request.ApiContext] Init() - JWTService() security.ApiSecurityService } -type handlerImpl struct{} +type handlerImpl struct { +} func (h *handlerImpl) UsersRepository() user.UsersRepository { return user.Repository() } -func (h *handlerImpl) ApiSecurityService() security.ApiSecurityService { - return security.GetApiSecurityService() +func (h *handlerImpl) ApiSecurityService() security.ApiSecurityService[*request.ApiContext] { + return security.GetApiSecurityService[*request.ApiContext](env.AppEnv().ApiSecretAuthorization) } -func Init(api server.ApiRouterHandler) { +func Init(api *server.ApiRouterHandler[*request.ApiContext]) { handler := handlerImpl{} - api.PublicRouter(handler.Login, "login", "POST") - api.Post(handler.CreateUser, "user", "POST", "resource:users:create:user") - api.Put(handler.UpdateUser, "user/:id", "resource:users:update:user") - api.Put(handler.UpdateUser, "user") + (*api).PublicRouter(handler.Login, "login", "POST") + (*api).Post(handler.CreateUser, "user", "POST", "resource:users:create:user") + (*api).Put(handler.UpdateUser, "user/:id", "resource:users:update:user") + (*api).Put(handler.UpdateUser, "user") } diff --git a/pkg/handlers/user/login.go b/pkg/handlers/user/login.go index fc40672..c8e3b91 100644 --- a/pkg/handlers/user/login.go +++ b/pkg/handlers/user/login.go @@ -2,7 +2,10 @@ package user import ( "errors" + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "time" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/sec" @@ -11,11 +14,11 @@ import ( "log" ) -func (h *handlerImpl) Login(ctx *server.ApiRequestContext) { +func (h *handlerImpl) Login(ctx *api_context.ApiRequestContext[*request.ApiContext]) { server.GetRequestBody(ctx, models.User{}, h.checkUserCredentials, server.FailedToLoadBody) } -func (h *handlerImpl) checkUserCredentials(ctx *server.ApiRequestContext, userInput models.User) { +func (h *handlerImpl) checkUserCredentials(ctx *api_context.ApiRequestContext[*request.ApiContext], userInput models.User) { decrypt, err := sec.Decrypt(userInput.Password, []byte(sec.SampleEncryptKey)) if err != nil { @@ -32,7 +35,7 @@ func (h *handlerImpl) checkUserCredentials(ctx *server.ApiRequestContext, userIn return } ctx.InternalServerError("Internal Server Error") - log.Printf("[%s]:: find user by username or email failed: %v", ctx.GetSessionId(), err) + log.Printf("[%s]:: find user_service by username or email failed: %v", ctx.GetSessionId(), err) return } @@ -43,7 +46,7 @@ func (h *handlerImpl) checkUserCredentials(ctx *server.ApiRequestContext, userIn } // Generate JWT and respond - tokenData, err := h.ApiSecurityService().GenerateJWT(*userResponse) + tokenData, err := h.ApiSecurityService().GenerateJWT(ctx.RequestData, time.Minute*10) if err != nil { log.Printf("[%s]:: generating new jwt failed: %v", ctx.GetSessionId(), err) ctx.InternalServerError("Error generating token") diff --git a/pkg/handlers/user/update.go b/pkg/handlers/user/update.go index 9af7fe6..bfbba5a 100644 --- a/pkg/handlers/user/update.go +++ b/pkg/handlers/user/update.go @@ -1,6 +1,7 @@ package user import ( + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" @@ -9,18 +10,17 @@ import ( "net/http" ) -func (h *handlerImpl) UpdateUser(ctx *server.ApiRequestContext) { +func (h *handlerImpl) UpdateUser(ctx *api_context.ApiRequestContext[*request.ApiContext]) { server.GetRequestBody(ctx, models.UserUpdate{}, h.useUpdateValidation, server.FailedToLoadBody) } -func (h *handlerImpl) useUpdateValidation(ctx *server.ApiRequestContext, updatedUser models.UserUpdate) { - apiContext := ctx.RequestData.(request.ApiContext) - currentUser, err := h.UsersRepository().FindUserBySalt(apiContext.AccessId) +func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*request.ApiContext], updatedUser models.UserUpdate) { + currentUser, err := h.UsersRepository().FindUserBySalt(ctx.RequestData.AccessId) if err != nil { - log.Printf("[%s]:: find user by salt failed: %v", ctx.GetSessionId(), err) + log.Printf("[%s]:: find user_service by salt failed: %v", ctx.GetSessionId(), err) - ctx.Error("Error finding user in the database", http.StatusInternalServerError) + ctx.Error("Error finding user_service in the database", http.StatusInternalServerError) return } @@ -36,14 +36,14 @@ func (h *handlerImpl) useUpdateValidation(ctx *server.ApiRequestContext, updated } } - // Save updated user to database + // Save updated user_service to database err = h.UsersRepository().Update(*currentUser) if err != nil { - log.Printf("[%s]:: updateing user failed: %v", ctx.GetSessionId(), err) - ctx.Error("Error updating user in the database", http.StatusInternalServerError) + log.Printf("[%s]:: updateing user_service failed: %v", ctx.GetSessionId(), err) + ctx.Error("Error updating user_service in the database", http.StatusInternalServerError) return } - log.Printf("[%s]:: user successfully updated", ctx.GetSessionId()) + log.Printf("[%s]:: user_service successfully updated", ctx.GetSessionId()) ctx.Ok(map[string]interface{}{"message": "User updated successfully"}) } diff --git a/pkg/handlers/user/user_handler/access_handler.go b/pkg/handlers/user/user_handler/access_handler.go new file mode 100644 index 0000000..dbea410 --- /dev/null +++ b/pkg/handlers/user/user_handler/access_handler.go @@ -0,0 +1,26 @@ +package user_handler + +import ( + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/wireguard-api/pkg/domain/service/user_service" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" +) + +type AuthenticationUserHandler interface { + Handler(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool +} + +type _AuthenticationUserHandlerImpl struct { + service *user_service.Service +} + +func GetAuthenticationUserHandler(service *user_service.Service) AuthenticationUserHandler { + return &_AuthenticationUserHandlerImpl{ + service: service, + } +} +func (h *_AuthenticationUserHandlerImpl) Handler(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool { + rolesLoader := (*h.service).LoadUserRoles + ctx.AccessRolesLoader = &rolesLoader + return true +} diff --git a/pkg/models/peer.go b/pkg/models/peer.go index 4911385..0ee73f3 100644 --- a/pkg/models/peer.go +++ b/pkg/models/peer.go @@ -2,7 +2,7 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" -// UsedPeer represents a requested peer by a user +// UsedPeer represents a requested peer by a user_service type UsedPeer struct { Username string `json:"username"` PeerData string `json:"peerData"` diff --git a/pkg/models/user.go b/pkg/models/user.go index 9bcbba0..df50926 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -4,7 +4,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// User represents a user object +// User represents a user_service object type User struct { Id primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"` Username string `json:"username" yaml:"username"` From 877c627a5a3711730cb26a8ce8a028dfccff8162 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Fri, 17 Jan 2025 21:21:21 -0300 Subject: [PATCH 2/9] Refactor to replace "user_service" with "user" across codebase Updated variable names, comments, and log messages to ensure consistent terminology by replacing "user_service" with "user". Simplified API initialization by modifying handler definitions and added a new API secret service for better key management. --- cmd/server/main.go | 18 ++++++++---- .../repository/user/users_repository_impl.go | 2 +- .../service/api_secret_service/service.go | 29 +++++++++++++++++++ pkg/domain/service/user_service/service.go | 4 +-- pkg/handlers/handlers.go | 2 +- pkg/handlers/peer/handlers.go | 6 ++-- pkg/handlers/request/context.go | 2 +- pkg/handlers/user/create.go | 6 ++-- pkg/handlers/user/handlers.go | 10 +++---- pkg/handlers/user/login.go | 2 +- pkg/handlers/user/update.go | 12 ++++---- pkg/models/peer.go | 2 +- pkg/models/user.go | 2 +- 13 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 pkg/domain/service/api_secret_service/service.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 1e71c5a..637d4fc 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,25 +1,33 @@ package main import ( + auth "github.com/softwareplace/http-utils/oauth" + "github.com/softwareplace/http-utils/security" "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/db" + "github.com/softwareplace/wireguard-api/pkg/domain/service/api_secret_service" "github.com/softwareplace/wireguard-api/pkg/domain/service/peer" "github.com/softwareplace/wireguard-api/pkg/domain/service/user_service" "github.com/softwareplace/wireguard-api/pkg/handlers" "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/handlers/user/user_handler" + "github.com/softwareplace/wireguard-api/pkg/utils/env" ) func main() { + appEnv := env.AppEnv() db.InitMongoDB() - api := server.New[*request.ApiContext]() - api.Use(request.ContextBuilder, "API/CONTEXT/INITIALIZER") - service := user_service.GetService() userAuthenticationUserHandler := user_handler.GetAuthenticationUserHandler(&service) - api.Use(userAuthenticationUserHandler.Handler, "MIDDLEWARE/AUTHENTICATION_USER") + api := server.New[*request.ApiContext]( + request.ContextBuilder, + userAuthenticationUserHandler.Handler, + ) - handlers.Init(&api) + secretService := api_secret_service.GetService() + securityService := security.GetApiSecurityService[*request.ApiContext](appEnv.ApiSecretAuthorization) + auth.Handler(appEnv.ApiSecretKey, secretService.GetKey, &securityService, api) + handlers.Init(api) peer.GetService().Load() service.Init() api.StartServer() diff --git a/pkg/domain/repository/user/users_repository_impl.go b/pkg/domain/repository/user/users_repository_impl.go index f54c2d4..d5a596f 100644 --- a/pkg/domain/repository/user/users_repository_impl.go +++ b/pkg/domain/repository/user/users_repository_impl.go @@ -39,7 +39,7 @@ func (r *usersRepositoryImpl) FindUserBySalt(salt string) (*models.User, error) }).Decode(¤tUser) if err != nil { - log.Printf("Error finding user_service by salt: %v", err) + log.Printf("Error finding user by salt: %v", err) return nil, err } return ¤tUser, nil diff --git a/pkg/domain/service/api_secret_service/service.go b/pkg/domain/service/api_secret_service/service.go new file mode 100644 index 0000000..21dcaa7 --- /dev/null +++ b/pkg/domain/service/api_secret_service/service.go @@ -0,0 +1,29 @@ +package api_secret_service + +import ( + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/wireguard-api/pkg/domain/repository/api_secret" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" +) + +type ApiSecretService interface { + GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) +} + +type serviceImpl struct { + repository api_secret.ApiSecretRepository +} + +func GetService() ApiSecretService { + return &serviceImpl{ + repository: api_secret.GetRepository(), + } +} + +func (s *serviceImpl) GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) { + apiSecret, err := s.repository.GetById(ctx.ApiKey) + if err != nil { + return "", err + } + return apiSecret.Key, nil +} diff --git a/pkg/domain/service/user_service/service.go b/pkg/domain/service/user_service/service.go index 0b2799c..2d28644 100644 --- a/pkg/domain/service/user_service/service.go +++ b/pkg/domain/service/user_service/service.go @@ -26,8 +26,8 @@ type serviceImpl struct { func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string { user, err := s.repository.FindUserBySalt(ctx.RequestData.AccessId) if err != nil { - log.Printf("[%s]:: error finding user_service: %v", ctx.GetSessionId(), err) - ctx.Error("Error finding user_service in the database", http.StatusInternalServerError) + log.Printf("[%s]:: error finding user: %v", ctx.GetSessionId(), err) + ctx.Error("Error finding user in the database", http.StatusInternalServerError) return nil } ctx.RequestData.User = user diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index c6ae1a2..63dac09 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -7,7 +7,7 @@ import ( "github.com/softwareplace/wireguard-api/pkg/handlers/user" ) -func Init(api *server.ApiRouterHandler[*request.ApiContext]) { +func Init(api server.ApiRouterHandler[*request.ApiContext]) { user.Init(api) peer.Init(api) } diff --git a/pkg/handlers/peer/handlers.go b/pkg/handlers/peer/handlers.go index 116b31f..fe2cdb1 100644 --- a/pkg/handlers/peer/handlers.go +++ b/pkg/handlers/peer/handlers.go @@ -23,8 +23,8 @@ func (h *handlerImpl) Service() peer.Service { return peer.GetService() } -func Init(api *server.ApiRouterHandler[*request.ApiContext]) { +func Init(api server.ApiRouterHandler[*request.ApiContext]) { handler := GetHandler() - (*api).Get(handler.GetAvailablePeer, "peers", "resource:peers:get:peer") - (*api).Post(handler.Stream, "peers/stream", "resource:peers:stream:peers") + api.Get(handler.GetAvailablePeer, "peers", "resource:peers:get:peer") + api.Post(handler.Stream, "peers/stream", "resource:peers:stream:peers") } diff --git a/pkg/handlers/request/context.go b/pkg/handlers/request/context.go index 87f38b5..25661ee 100644 --- a/pkg/handlers/request/context.go +++ b/pkg/handlers/request/context.go @@ -57,7 +57,7 @@ func (ctx *ApiContext) GetRoles() (roles []string, err error) { if user != nil && len(user.Roles) > 0 { return user.Roles, nil } - return nil, fmt.Errorf("user_service roles not found") + return nil, fmt.Errorf("user roles not found") } func (ctx *ApiContext) SetUser(user *models.User) { diff --git a/pkg/handlers/user/create.go b/pkg/handlers/user/create.go index d6e609b..486c3e7 100644 --- a/pkg/handlers/user/create.go +++ b/pkg/handlers/user/create.go @@ -43,11 +43,11 @@ func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*req user.Status = "ACTIVE" if err := h.UsersRepository().Save(user); err != nil { - log.Printf("[%s]:: error saving user_service to the database: %v", ctx.GetSessionId(), err) - ctx.Error("Error saving user_service to the database", http.StatusInternalServerError) + log.Printf("[%s]:: error saving user to the database: %v", ctx.GetSessionId(), err) + ctx.Error("Error saving user to the database", http.StatusInternalServerError) return } - log.Printf("[%s]:: user_service created successfully", ctx.GetSessionId()) + log.Printf("[%s]:: user created successfully", ctx.GetSessionId()) ctx.Created(map[string]interface{}{"message": "User created successfully"}) } diff --git a/pkg/handlers/user/handlers.go b/pkg/handlers/user/handlers.go index 12ad291..61341cf 100644 --- a/pkg/handlers/user/handlers.go +++ b/pkg/handlers/user/handlers.go @@ -29,10 +29,10 @@ func (h *handlerImpl) ApiSecurityService() security.ApiSecurityService[*request. return security.GetApiSecurityService[*request.ApiContext](env.AppEnv().ApiSecretAuthorization) } -func Init(api *server.ApiRouterHandler[*request.ApiContext]) { +func Init(api server.ApiRouterHandler[*request.ApiContext]) { handler := handlerImpl{} - (*api).PublicRouter(handler.Login, "login", "POST") - (*api).Post(handler.CreateUser, "user", "POST", "resource:users:create:user") - (*api).Put(handler.UpdateUser, "user/:id", "resource:users:update:user") - (*api).Put(handler.UpdateUser, "user") + api.PublicRouter(handler.Login, "login", "POST") + api.Post(handler.CreateUser, "user", "POST", "resource:users:create:user") + api.Put(handler.UpdateUser, "user/:id", "resource:users:update:user") + api.Put(handler.UpdateUser, "user") } diff --git a/pkg/handlers/user/login.go b/pkg/handlers/user/login.go index c8e3b91..ae77237 100644 --- a/pkg/handlers/user/login.go +++ b/pkg/handlers/user/login.go @@ -35,7 +35,7 @@ func (h *handlerImpl) checkUserCredentials(ctx *api_context.ApiRequestContext[*r return } ctx.InternalServerError("Internal Server Error") - log.Printf("[%s]:: find user_service by username or email failed: %v", ctx.GetSessionId(), err) + log.Printf("[%s]:: find user by username or email failed: %v", ctx.GetSessionId(), err) return } diff --git a/pkg/handlers/user/update.go b/pkg/handlers/user/update.go index bfbba5a..0bf7802 100644 --- a/pkg/handlers/user/update.go +++ b/pkg/handlers/user/update.go @@ -18,9 +18,9 @@ func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*re currentUser, err := h.UsersRepository().FindUserBySalt(ctx.RequestData.AccessId) if err != nil { - log.Printf("[%s]:: find user_service by salt failed: %v", ctx.GetSessionId(), err) + log.Printf("[%s]:: find user by salt failed: %v", ctx.GetSessionId(), err) - ctx.Error("Error finding user_service in the database", http.StatusInternalServerError) + ctx.Error("Error finding user in the database", http.StatusInternalServerError) return } @@ -36,14 +36,14 @@ func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*re } } - // Save updated user_service to database + // Save updated user to database err = h.UsersRepository().Update(*currentUser) if err != nil { - log.Printf("[%s]:: updateing user_service failed: %v", ctx.GetSessionId(), err) - ctx.Error("Error updating user_service in the database", http.StatusInternalServerError) + log.Printf("[%s]:: updateing user failed: %v", ctx.GetSessionId(), err) + ctx.Error("Error updating user in the database", http.StatusInternalServerError) return } - log.Printf("[%s]:: user_service successfully updated", ctx.GetSessionId()) + log.Printf("[%s]:: user successfully updated", ctx.GetSessionId()) ctx.Ok(map[string]interface{}{"message": "User updated successfully"}) } diff --git a/pkg/models/peer.go b/pkg/models/peer.go index 0ee73f3..4911385 100644 --- a/pkg/models/peer.go +++ b/pkg/models/peer.go @@ -2,7 +2,7 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" -// UsedPeer represents a requested peer by a user_service +// UsedPeer represents a requested peer by a user type UsedPeer struct { Username string `json:"username"` PeerData string `json:"peerData"` diff --git a/pkg/models/user.go b/pkg/models/user.go index df50926..9bcbba0 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -4,7 +4,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// User represents a user_service object +// User represents a user object type User struct { Id primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"` Username string `json:"username" yaml:"username"` From 971c906ebf7d3ae3c94a6f0c7fa0f8fc997b4ab8 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Fri, 17 Jan 2025 21:52:12 -0300 Subject: [PATCH 3/9] Update dependencies and refactor user service implementation Upgraded "github.com/softwareplace/http-utils" to the latest version in go.mod and go.sum. Refactored user service to use security service and adjusted method calls to align with updated structures. Removed unused parameter in SetRoles method to clean up code. --- go.mod | 2 +- pkg/domain/service/user_service/service.go | 13 ++++++++----- pkg/handlers/request/context.go | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9feccb8..0308659 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.23 toolchain go1.23.4 require ( + github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 github.com/atotto/clipboard v0.1.4 github.com/google/uuid v1.6.0 - github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd go.mongodb.org/mongo-driver v1.17.2 golang.org/x/crypto v0.32.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/pkg/domain/service/user_service/service.go b/pkg/domain/service/user_service/service.go index 2d28644..fb577c1 100644 --- a/pkg/domain/service/user_service/service.go +++ b/pkg/domain/service/user_service/service.go @@ -2,6 +2,7 @@ package user_service import ( "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/security" repo "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" @@ -19,12 +20,13 @@ type Service interface { } type serviceImpl struct { - appEnv env.ApplicationEnv - repository repo.UsersRepository + appEnv env.ApplicationEnv + repository repo.UsersRepository + securityService security.ApiSecurityService[*request.ApiContext] } func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string { - user, err := s.repository.FindUserBySalt(ctx.RequestData.AccessId) + user, err := s.repository.FindUserBySalt(ctx.RequestData.Salt()) if err != nil { log.Printf("[%s]:: error finding user: %v", ctx.GetSessionId(), err) ctx.Error("Error finding user in the database", http.StatusInternalServerError) @@ -36,8 +38,9 @@ func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.A func GetService() Service { return &serviceImpl{ - appEnv: env.AppEnv(), - repository: repo.Repository(), + appEnv: env.AppEnv(), + repository: repo.Repository(), + securityService: security.GetApiSecurityService[*request.ApiContext](env.AppEnv().ApiSecretAuthorization), } } diff --git a/pkg/handlers/request/context.go b/pkg/handlers/request/context.go index 25661ee..ffbc0a0 100644 --- a/pkg/handlers/request/context.go +++ b/pkg/handlers/request/context.go @@ -80,7 +80,7 @@ func (ctx *ApiContext) SetAccessId(accessId string) { ctx.AccessId = accessId } -func (ctx *ApiContext) SetRoles(roles []string) { +func (ctx *ApiContext) SetRoles([]string) { } func ContextBuilder(ctx *api_context.ApiRequestContext[*ApiContext]) bool { From a9603cec90b09011778f822bf395c086426684d6 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Fri, 17 Jan 2025 22:14:42 -0300 Subject: [PATCH 4/9] Refactor API security and user handling for clarity Updated API security service contexts and request data handling for better type safety and coherence. Improved user role loading and key retrieval logic, ensuring accurate and consistent access controls. Adjusted MongoDB initialization order to optimize server startup sequence. --- cmd/generator/api_secret/main.go | 7 ++++--- cmd/server/main.go | 5 ++++- go.sum | 2 ++ .../service/api_secret_service/service.go | 2 +- pkg/domain/service/user_service/service.go | 18 ++++++++++-------- pkg/handlers/user/login.go | 1 + 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/cmd/generator/api_secret/main.go b/cmd/generator/api_secret/main.go index d3d5dbb..cdc1c25 100644 --- a/cmd/generator/api_secret/main.go +++ b/cmd/generator/api_secret/main.go @@ -98,7 +98,7 @@ func main() { Bytes: publicKeyBytes, }) - encryptedKey, err := security.GetApiSecurityService[api_context.DefaultContext](appEnv.ApiSecretAuthorization).Encrypt(string(publicKeyPEM)) + encryptedKey, err := security.GetApiSecurityService[*api_context.DefaultContext](appEnv.ApiSecretAuthorization).Encrypt(string(publicKeyPEM)) if err != nil { log.Fatalf("Failed to sec public key: %s", err) @@ -120,13 +120,14 @@ func main() { return } + expirationToken := time.Hour * (time.Duration(*expirationHours)) apiJWTInfo := security.ApiJWTInfo{ Client: *clientInfo, - Expiration: time.Duration(*expirationHours), + Expiration: expirationToken, Key: *id, } - apiSecretJWT, err := security.GetApiSecurityService[api_context.DefaultContext](appEnv.ApiSecretAuthorization).GenerateApiSecretJWT(apiJWTInfo) + apiSecretJWT, err := security.GetApiSecurityService[*api_context.DefaultContext](appEnv.ApiSecretAuthorization).GenerateApiSecretJWT(apiJWTInfo) if err != nil { return diff --git a/cmd/server/main.go b/cmd/server/main.go index 637d4fc..bdf3e34 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,9 +16,9 @@ import ( func main() { appEnv := env.AppEnv() - db.InitMongoDB() service := user_service.GetService() userAuthenticationUserHandler := user_handler.GetAuthenticationUserHandler(&service) + api := server.New[*request.ApiContext]( request.ContextBuilder, userAuthenticationUserHandler.Handler, @@ -28,6 +28,9 @@ func main() { securityService := security.GetApiSecurityService[*request.ApiContext](appEnv.ApiSecretAuthorization) auth.Handler(appEnv.ApiSecretKey, secretService.GetKey, &securityService, api) handlers.Init(api) + + db.InitMongoDB() + peer.GetService().Load() service.Init() api.StartServer() diff --git a/go.sum b/go.sum index 9e96caf..38bd330 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6 h1:uxMnbp github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd h1:NTHsh5xr4DlIGwaNrEuq7vREYryGPZol7qFYsQgPl8s= github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= +github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 h1:pTZv6az0E/CQBbVXUROyWQjdaFyamMMkz2YYvRf9CmI= +github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= diff --git a/pkg/domain/service/api_secret_service/service.go b/pkg/domain/service/api_secret_service/service.go index 21dcaa7..f3eb974 100644 --- a/pkg/domain/service/api_secret_service/service.go +++ b/pkg/domain/service/api_secret_service/service.go @@ -21,7 +21,7 @@ func GetService() ApiSecretService { } func (s *serviceImpl) GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) { - apiSecret, err := s.repository.GetById(ctx.ApiKey) + apiSecret, err := s.repository.GetById(ctx.RequestData.ApiKeyId) if err != nil { return "", err } diff --git a/pkg/domain/service/user_service/service.go b/pkg/domain/service/user_service/service.go index fb577c1..6d7ae53 100644 --- a/pkg/domain/service/user_service/service.go +++ b/pkg/domain/service/user_service/service.go @@ -11,7 +11,6 @@ import ( "github.com/softwareplace/wireguard-api/pkg/utils/sec" "github.com/softwareplace/wireguard-api/pkg/utils/validator" "log" - "net/http" ) type Service interface { @@ -26,14 +25,17 @@ type serviceImpl struct { } func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string { - user, err := s.repository.FindUserBySalt(ctx.RequestData.Salt()) - if err != nil { - log.Printf("[%s]:: error finding user: %v", ctx.GetSessionId(), err) - ctx.Error("Error finding user in the database", http.StatusInternalServerError) - return nil + if s.securityService.ExtractJWTClaims(ctx) { + user, err := s.repository.FindUserBySalt(ctx.RequestData.AccessId) + if err != nil { + log.Printf("[%s]:: error finding user: %v", ctx.GetSessionId(), err) + return nil + } + ctx.RequestData.User = user + return user.Roles } - ctx.RequestData.User = user - return user.Roles + + return nil } func GetService() Service { diff --git a/pkg/handlers/user/login.go b/pkg/handlers/user/login.go index ae77237..eb58ecb 100644 --- a/pkg/handlers/user/login.go +++ b/pkg/handlers/user/login.go @@ -45,6 +45,7 @@ func (h *handlerImpl) checkUserCredentials(ctx *api_context.ApiRequestContext[*r return } + ctx.RequestData.User = userResponse // Generate JWT and respond tokenData, err := h.ApiSecurityService().GenerateJWT(ctx.RequestData, time.Minute*10) if err != nil { From dd17ec617503dbcc05c0eec67f50f399442d74dd Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Fri, 17 Jan 2025 22:20:46 -0300 Subject: [PATCH 5/9] Refactor API key header handling and update status codes. Replaced `httputilsserver.XApiKey` with `api_context.XApiKey` for consistency across modules. Adjusted expected HTTP status in `cmd/stream/main.go` to `http.StatusOK`. Cleaned up redundant module entries in `go.mod` and `go.sum`. --- cmd/cli/connect/get_peer.go | 4 ++-- cmd/cli/connect/login.go | 4 ++-- cmd/stream/main.go | 2 +- go.mod | 2 +- go.sum | 10 ---------- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/cmd/cli/connect/get_peer.go b/cmd/cli/connect/get_peer.go index a5e149a..78453c1 100644 --- a/cmd/cli/connect/get_peer.go +++ b/cmd/cli/connect/get_peer.go @@ -1,8 +1,8 @@ package connect import ( + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/request" - httputilsserver "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/cmd/cli/spec" "github.com/softwareplace/wireguard-api/pkg/models" "log" @@ -14,7 +14,7 @@ func GetPeer(profile *spec.Profile, server *spec.Server) models.Peer { apiConfig := request.Build(server.Host). WithPath("/peers"). - WithHeader(httputilsserver.XApiKey, server.ApiKey). + WithHeader(api_context.XApiKey, server.ApiKey). WithHeader("Authorization", profile.AuthorizationKey). WithExpectedStatusCode(http.StatusOK) diff --git a/cmd/cli/connect/login.go b/cmd/cli/connect/login.go index 8024656..c925971 100644 --- a/cmd/cli/connect/login.go +++ b/cmd/cli/connect/login.go @@ -2,8 +2,8 @@ package connect import ( "fmt" + "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/request" - httputilsserver "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/cmd/cli/shared" "github.com/softwareplace/wireguard-api/cmd/cli/spec" "github.com/softwareplace/wireguard-api/pkg/utils/sec" @@ -76,7 +76,7 @@ func Login(args *shared.Args, profile *spec.Profile, server spec.Server) { config := request.Build(server.Host). WithPath("/login"). WithBody(reqBody). - WithHeader(httputilsserver.XApiKey, server.ApiKey). + WithHeader(api_context.XApiKey, server.ApiKey). WithExpectedStatusCode(http.StatusOK) loginResp, err := api.Post(config) diff --git a/cmd/stream/main.go b/cmd/stream/main.go index a4deb21..88e628b 100644 --- a/cmd/stream/main.go +++ b/cmd/stream/main.go @@ -53,7 +53,7 @@ func main() { WithHeader("Authorization", streamEnv.Authorization). WithHeader(api_context.XApiKey, streamEnv.ApiKey). WithBody(dump). - WithExpectedStatusCode(http.StatusCreated) + WithExpectedStatusCode(http.StatusOK) _, err = api.Post(config) if err != nil { diff --git a/go.mod b/go.mod index 0308659..9cc6398 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.23 toolchain go1.23.4 require ( - github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 github.com/atotto/clipboard v0.1.4 github.com/google/uuid v1.6.0 + github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 go.mongodb.org/mongo-driver v1.17.2 golang.org/x/crypto v0.32.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 38bd330..27207a7 100644 --- a/go.sum +++ b/go.sum @@ -16,16 +16,6 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/softwareplace/http-utils v0.0.0-20250116230044-ed510c97fc93 h1:ozum1P9qUdOMrItHokPNVHbOHZNObM2s2fYBWo3bxe0= -github.com/softwareplace/http-utils v0.0.0-20250116230044-ed510c97fc93/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= -github.com/softwareplace/http-utils v0.0.0-20250116232827-e496aa73762e h1:PngEYXx329HwewdTbn/g0iKcozTlvR0Y61a+1Q2fYtE= -github.com/softwareplace/http-utils v0.0.0-20250116232827-e496aa73762e/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= -github.com/softwareplace/http-utils v0.0.0-20250117012334-addd8fbd1220 h1:4CYLGGScSzkWEnV5rxbgGmEWpYcPwgpXb958LnXp3+g= -github.com/softwareplace/http-utils v0.0.0-20250117012334-addd8fbd1220/go.mod h1:GhSRH3bwg1iK6CZl3IwjPol9t+QctcPrVPc3mvZmQ7Q= -github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6 h1:uxMnbpDzjwnTeJNT9W5NZzkbITRG1CzS5BbkLMD39k0= -github.com/softwareplace/http-utils v0.0.0-20250117224835-13ee112340a6/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= -github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd h1:NTHsh5xr4DlIGwaNrEuq7vREYryGPZol7qFYsQgPl8s= -github.com/softwareplace/http-utils v0.0.0-20250117231145-c67f294855cd/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 h1:pTZv6az0E/CQBbVXUROyWQjdaFyamMMkz2YYvRf9CmI= github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= From 4caa4f91fed8ab146ce0357538df77e1e534dd0d Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Sat, 18 Jan 2025 09:00:09 -0300 Subject: [PATCH 6/9] Upgrade dependencies and improve configuration handling. Upgraded Golang image to 1.23 and adjusted environment variables for better flexibility, including support for `DEBUG_MODE`. Enhanced logging functionality and streamlined Docker and MongoDB initialization processes. Minor fixes in file paths and documentation updates were also made. --- DockerfileBuild | 4 +--- Makefile | 3 ++- README.md | 1 + cmd/server/main.go | 4 ++-- dev/.env | 5 +++-- docker-compose.yml | 1 + pkg/domain/db/db.go | 3 ++- pkg/utils/env/envs.go | 6 ++++++ scripts/docker/compiler/build | 2 +- 9 files changed, 19 insertions(+), 10 deletions(-) diff --git a/DockerfileBuild b/DockerfileBuild index e65043c..7ecebbb 100644 --- a/DockerfileBuild +++ b/DockerfileBuild @@ -1,5 +1,5 @@ # Use the official Golang image -FROM golang:1.22.5-alpine as builder +FROM golang:1.23-alpine as builder # Set the working directory WORKDIR /app @@ -24,8 +24,6 @@ FROM alpine:latest # Install necessary dependencies RUN apk --no-cache add ca-certificates -RUN mkdir -p /output - # Copy the built app from the builder stage COPY --from=builder /app/wireguard-api /bin/wireguard-api COPY --from=builder /app/api-key-generator /bin/api-key-generator diff --git a/Makefile b/Makefile index e7f6d46..5277f4b 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ ENV = ./dev/.env up: @$(DOCKER_COMPOSE) --env-file $(ENV) --project-name private-network -f $(DOCKER_COMPOSE_FILE) up -d @echo "Docker services are now running." + @make logs # Stop all running services down: @@ -28,7 +29,7 @@ rebuild: # Show logs from Docker Compose services logs: - @$(DOCKER_COMPOSE) --project-name wireguard-api -f $(DOCKER_COMPOSE_FILE) logs -f + @docker logs -f wireguard-api # Clean up dangling images, stopped containers, and unused networks clean: diff --git a/README.md b/README.md index f27a874..9ec49c9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The application reads its configuration from the following environment variables | `CONTEXT_PATH` | The base path used for API routing. | `/api/private-network/v1/` | No | | `PEERS_RESOURCE_PATH` | Filesystem path for peer resource connections. | `/etc/wireguard/` | No | | `API_INIT_FILE` | Add the first user data that will be created at the application startup | N/A | No | +| `DEBUG_MODE` | Set `log.SetFlags(log.LstdFlags \| log.Llongfile)` | N/A | No | ### Database Configuration (`DBEnv`) diff --git a/cmd/server/main.go b/cmd/server/main.go index bdf3e34..3e11dbd 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,6 +16,8 @@ import ( func main() { appEnv := env.AppEnv() + db.InitMongoDB() + service := user_service.GetService() userAuthenticationUserHandler := user_handler.GetAuthenticationUserHandler(&service) @@ -29,8 +31,6 @@ func main() { auth.Handler(appEnv.ApiSecretKey, secretService.GetKey, &securityService, api) handlers.Init(api) - db.InitMongoDB() - peer.GetService().Load() service.Init() api.StartServer() diff --git a/dev/.env b/dev/.env index f8b8e52..20a4b6f 100644 --- a/dev/.env +++ b/dev/.env @@ -6,9 +6,10 @@ MONGO_PORT=27017 MONGO_CONTAINER_NAME=wireguard-api-mongo # WireGuard API Configuration -PORT=8080 +PORT=1080 PEERS_RESOURCE_PATH=./dev/peers -API_SECRET_PATH=./dev/v1/secret +CONTEXT_PATH=/api/private-network/v1/ +API_SECRET_PATH=./dev/secret/v1/ API_SECRET_KEY=/etc/secret/private.key APP_CONTAINER_NAME=wireguard-api MONGO_URI=mongodb://${MONGO_CONTAINER_NAME}:${MONGO_PORT} diff --git a/docker-compose.yml b/docker-compose.yml index 9b6cd62..95a0298 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: - ${PEERS_RESOURCE_PATH}:/etc/wireguard - ${API_SECRET_PATH}:/etc/secret environment: + - CONTEXT_PATH=${CONTEXT_PATH} - API_SECRET_KEY=${API_SECRET_KEY} - MONGO_URI=${MONGO_URI} - MONGO_USERNAME=${MONGO_INITDB_ROOT_USERNAME} diff --git a/pkg/domain/db/db.go b/pkg/domain/db/db.go index 501509f..53c70fa 100644 --- a/pkg/domain/db/db.go +++ b/pkg/domain/db/db.go @@ -8,9 +8,10 @@ import ( "log" ) -var dbEnv = env.AppEnv().DBEnv +var dbEnv env.DBEnv func InitMongoDB() { + dbEnv = env.AppEnv().DBEnv connectionChecker() GetDB() } diff --git a/pkg/utils/env/envs.go b/pkg/utils/env/envs.go index b785c84..d2cf209 100644 --- a/pkg/utils/env/envs.go +++ b/pkg/utils/env/envs.go @@ -1,6 +1,7 @@ package env import ( + "log" "os" ) @@ -34,7 +35,12 @@ type DBEnv struct { var appEnv *ApplicationEnv func AppEnv() ApplicationEnv { + if os.Getenv("DEBUG_MODE") == "true" { + log.SetFlags(log.LstdFlags | log.Llongfile) + } + if appEnv == nil { + dbEnv := DBEnv{ DatabaseName: GetRequiredEnv("MONGO_DATABASE"), // required Username: GetRequiredEnv("MONGO_USERNAME"), // required diff --git a/scripts/docker/compiler/build b/scripts/docker/compiler/build index 18ef1c6..75006bf 100644 --- a/scripts/docker/compiler/build +++ b/scripts/docker/compiler/build @@ -3,7 +3,7 @@ mkdir -p /output cp /bin/wireguard-api /output/wireguard-api -cp /bin/api-key-generator /output/api-key-generator +cp /bin/api-key-generator /output/api-key-generator cp /bin/wireguard-tools /output/wireguard-tools cp /bin/wireguard-stream /output/wireguard-stream From d9d3456dfc18f76be9fa65ce0d873e8cb46e20e7 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Sat, 18 Jan 2025 19:17:31 -0300 Subject: [PATCH 7/9] Refactor user handling to improve login and principal services. Replaced deprecated user login and access handler logic with a new modular `loginServiceImpl` and `UserPrincipalService`. Streamlined repository access, removed redundant methods, and migrated key functionalities to services for better maintainability and scalability. Updated API router initialization and cleaned up user request context. --- cmd/server/main.go | 31 ++++--- go.mod | 2 +- go.sum | 10 +++ pkg/domain/db/db.go | 18 ++-- .../repository/user/users_repository.go | 17 +++- .../service.go | 4 +- .../user_principal_service.go | 40 +++++++++ .../service/user_service/login_service.go | 39 ++++++++ pkg/domain/service/user_service/service.go | 32 ++----- pkg/handlers/request/context.go | 88 +++---------------- pkg/handlers/user/create.go | 4 +- pkg/handlers/user/handlers.go | 8 +- pkg/handlers/user/login.go | 58 ------------ pkg/handlers/user/update.go | 4 +- .../user/user_handler/access_handler.go | 26 ------ pkg/models/user.go | 11 +++ 16 files changed, 170 insertions(+), 222 deletions(-) rename pkg/domain/service/{api_secret_service => apiSecretService}/service.go (87%) create mode 100644 pkg/domain/service/userPrincipalService/user_principal_service.go create mode 100644 pkg/domain/service/user_service/login_service.go delete mode 100644 pkg/handlers/user/login.go delete mode 100644 pkg/handlers/user/user_handler/access_handler.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 3e11dbd..ece75a4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,16 +1,15 @@ package main import ( - auth "github.com/softwareplace/http-utils/oauth" "github.com/softwareplace/http-utils/security" "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/db" - "github.com/softwareplace/wireguard-api/pkg/domain/service/api_secret_service" + "github.com/softwareplace/wireguard-api/pkg/domain/service/apiSecretService" "github.com/softwareplace/wireguard-api/pkg/domain/service/peer" + "github.com/softwareplace/wireguard-api/pkg/domain/service/userPrincipalService" "github.com/softwareplace/wireguard-api/pkg/domain/service/user_service" "github.com/softwareplace/wireguard-api/pkg/handlers" "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/handlers/user/user_handler" "github.com/softwareplace/wireguard-api/pkg/utils/env" ) @@ -18,20 +17,26 @@ func main() { appEnv := env.AppEnv() db.InitMongoDB() - service := user_service.GetService() - userAuthenticationUserHandler := user_handler.GetAuthenticationUserHandler(&service) + userService := user_service.GetService() + principalService := userPrincipalService.New() + secretService := apiSecretService.GetService() - api := server.New[*request.ApiContext]( - request.ContextBuilder, - userAuthenticationUserHandler.Handler, + securityService := security.ApiSecurityServiceBuild(appEnv.ApiSecretAuthorization, &principalService) + secreteAccessHandler := security.ApiSecretAccessHandlerBuild( + appEnv.ApiSecretKey, + secretService.GetKey, + securityService, ) - secretService := api_secret_service.GetService() - securityService := security.GetApiSecurityService[*request.ApiContext](appEnv.ApiSecretAuthorization) - auth.Handler(appEnv.ApiSecretKey, secretService.GetKey, &securityService, api) - handlers.Init(api) + userLoginService := user_service.New(securityService) + + api := server.CreateApiRouter[*request.ApiContext](). + RegisterMiddleware(secreteAccessHandler.HandlerSecretAccess, security.ApiSecretAccessHandlerName). + RegisterMiddleware(securityService.AuthorizationHandler, security.ApiSecurityHandlerName). + WithLoginResource(&userLoginService) + handlers.Init(api) + userService.Init() peer.GetService().Load() - service.Init() api.StartServer() } diff --git a/go.mod b/go.mod index 9cc6398..52fe53b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.23.4 require ( github.com/atotto/clipboard v0.1.4 github.com/google/uuid v1.6.0 - github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 + github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b go.mongodb.org/mongo-driver v1.17.2 golang.org/x/crypto v0.32.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 27207a7..f20fbef 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,16 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8 github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 h1:pTZv6az0E/CQBbVXUROyWQjdaFyamMMkz2YYvRf9CmI= github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= +github.com/softwareplace/http-utils v0.0.0-20250118120434-879af96930a9 h1:WPyYuPDF0CTXFLXiQ8OPRX9qeYUMqcJqHXq/hUFbrWc= +github.com/softwareplace/http-utils v0.0.0-20250118120434-879af96930a9/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= +github.com/softwareplace/http-utils v0.0.0-20250118162347-a3eeb79c28ea h1:YtZQmFVbLSLy+lnaKvYJXk+ftQXESKhKq+QNy2FEJsU= +github.com/softwareplace/http-utils v0.0.0-20250118162347-a3eeb79c28ea/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250118203853-db4f95982ab9 h1:bVODWNdlh6nG2hdIwgzZS5KAFRDr4RA9YEn7Ol4vQbg= +github.com/softwareplace/http-utils v0.0.0-20250118203853-db4f95982ab9/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250118210746-bc0ebc16842b h1:dLH6T4tSO35Penou61Re1c8z1eJxBleTzcCh5aQgD1Y= +github.com/softwareplace/http-utils v0.0.0-20250118210746-bc0ebc16842b/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b h1:v4r1YBVnRtvn/umb2jsZ+ETwo5vd7a07b/rbxi2qrDQ= +github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= diff --git a/pkg/domain/db/db.go b/pkg/domain/db/db.go index 53c70fa..0480151 100644 --- a/pkg/domain/db/db.go +++ b/pkg/domain/db/db.go @@ -6,9 +6,21 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "log" + "sync" ) -var dbEnv env.DBEnv +var ( + dbEnv env.DBEnv + once sync.Once + db *mongo.Database +) + +func GetDB() *mongo.Database { + once.Do(func() { + db = GetDBClient().Database(dbEnv.DatabaseName) + }) + return db +} func InitMongoDB() { dbEnv = env.AppEnv().DBEnv @@ -16,10 +28,6 @@ func InitMongoDB() { GetDB() } -func GetDB() *mongo.Database { - return GetDBClient().Database(dbEnv.DatabaseName) -} - func GetDBClient() *mongo.Client { username := dbEnv.Username diff --git a/pkg/domain/repository/user/users_repository.go b/pkg/domain/repository/user/users_repository.go index 7086a59..59a761e 100644 --- a/pkg/domain/repository/user/users_repository.go +++ b/pkg/domain/repository/user/users_repository.go @@ -4,6 +4,7 @@ import ( "github.com/softwareplace/wireguard-api/pkg/domain/db" "github.com/softwareplace/wireguard-api/pkg/models" "go.mongodb.org/mongo-driver/mongo" + "sync" ) type UsersRepository interface { @@ -23,8 +24,16 @@ func (r *usersRepositoryImpl) collection() *mongo.Collection { return r.database.Collection("users") } -func Repository() UsersRepository { - return &usersRepositoryImpl{ - database: db.GetDB(), - } +var ( + repository UsersRepository + once sync.Once +) + +func Repository() *UsersRepository { + once.Do(func() { + repository = &usersRepositoryImpl{ + database: db.GetDB(), + } + }) + return &repository } diff --git a/pkg/domain/service/api_secret_service/service.go b/pkg/domain/service/apiSecretService/service.go similarity index 87% rename from pkg/domain/service/api_secret_service/service.go rename to pkg/domain/service/apiSecretService/service.go index f3eb974..e597282 100644 --- a/pkg/domain/service/api_secret_service/service.go +++ b/pkg/domain/service/apiSecretService/service.go @@ -1,4 +1,4 @@ -package api_secret_service +package apiSecretService import ( "github.com/softwareplace/http-utils/api_context" @@ -21,7 +21,7 @@ func GetService() ApiSecretService { } func (s *serviceImpl) GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) { - apiSecret, err := s.repository.GetById(ctx.RequestData.ApiKeyId) + apiSecret, err := s.repository.GetById(ctx.ApiKeyId) if err != nil { return "", err } diff --git a/pkg/domain/service/userPrincipalService/user_principal_service.go b/pkg/domain/service/userPrincipalService/user_principal_service.go new file mode 100644 index 0000000..b58e5bd --- /dev/null +++ b/pkg/domain/service/userPrincipalService/user_principal_service.go @@ -0,0 +1,40 @@ +package userPrincipalService + +import ( + "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/security/principal" + "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "sync" +) + +type UserPrincipalService struct { + userRepository user.UsersRepository +} + +var ( + once sync.Once + service principal.PService[*request.ApiContext] +) + +func New() principal.PService[*request.ApiContext] { + once.Do(func() { + service = &UserPrincipalService{ + userRepository: *user.Repository(), + } + }) + return service +} + +func (u *UserPrincipalService) LoadPrincipal(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool { + userResponse, err := u.userRepository.FindUserBySalt(ctx.Principal.GetSalt()) + if err != nil { + return false + } + + ctx.Principal = &request.ApiContext{ + User: userResponse.Parse(), + } + + return true +} diff --git a/pkg/domain/service/user_service/login_service.go b/pkg/domain/service/user_service/login_service.go new file mode 100644 index 0000000..184e55a --- /dev/null +++ b/pkg/domain/service/user_service/login_service.go @@ -0,0 +1,39 @@ +package user_service + +import ( + "github.com/softwareplace/http-utils/security" + "github.com/softwareplace/http-utils/server" + "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "time" +) + +type loginServiceImpl struct { + securityService security.ApiSecurityService[*request.ApiContext] + repository *user.UsersRepository +} + +func (l *loginServiceImpl) SecurityService() security.ApiSecurityService[*request.ApiContext] { + return l.securityService +} + +func New(securityService security.ApiSecurityService[*request.ApiContext]) server.LoginService[*request.ApiContext] { + return &loginServiceImpl{ + securityService: securityService, + repository: user.Repository(), + } +} + +func (l *loginServiceImpl) Login(user server.LoginEntryData) (*request.ApiContext, error) { + response, err := (*l.repository).FindUserByUsernameOrEmail(user.Username, user.Email) + if err != nil { + return nil, err + } + return &request.ApiContext{ + User: response.Parse(), + }, nil +} + +func (l *loginServiceImpl) TokenDuration() time.Duration { + return time.Minute * 15 +} diff --git a/pkg/domain/service/user_service/service.go b/pkg/domain/service/user_service/service.go index 6d7ae53..20aa1d3 100644 --- a/pkg/domain/service/user_service/service.go +++ b/pkg/domain/service/user_service/service.go @@ -1,10 +1,7 @@ package user_service import ( - "github.com/softwareplace/http-utils/api_context" - "github.com/softwareplace/http-utils/security" repo "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/env" "github.com/softwareplace/wireguard-api/pkg/utils/file" @@ -15,34 +12,17 @@ import ( type Service interface { Init() - LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string } type serviceImpl struct { - appEnv env.ApplicationEnv - repository repo.UsersRepository - securityService security.ApiSecurityService[*request.ApiContext] -} - -func (s *serviceImpl) LoadUserRoles(ctx api_context.ApiRequestContext[*request.ApiContext]) []string { - if s.securityService.ExtractJWTClaims(ctx) { - user, err := s.repository.FindUserBySalt(ctx.RequestData.AccessId) - if err != nil { - log.Printf("[%s]:: error finding user: %v", ctx.GetSessionId(), err) - return nil - } - ctx.RequestData.User = user - return user.Roles - } - - return nil + appEnv env.ApplicationEnv + repository *repo.UsersRepository } func GetService() Service { return &serviceImpl{ - appEnv: env.AppEnv(), - repository: repo.Repository(), - securityService: security.GetApiSecurityService[*request.ApiContext](env.AppEnv().ApiSecretAuthorization), + appEnv: env.AppEnv(), + repository: repo.Repository(), } } @@ -63,7 +43,7 @@ func (s *serviceImpl) Init() { log.Fatalf("Failed to validate init user data: %v", err) } - _, err = s.repository.FindUserByUsernameOrEmail(initUserData.Username, initUserData.Email) + _, err = (*s.repository).FindUserByUsernameOrEmail(initUserData.Username, initUserData.Email) if err == nil { return @@ -79,7 +59,7 @@ func (s *serviceImpl) Init() { initUserData.Salt = salt initUserData.Status = "ACTIVE" - err = s.repository.Save(initUserData) + err = (*s.repository).Save(initUserData) if err != nil { log.Fatalf("Failed to save init user data: %v", err) diff --git a/pkg/handlers/request/context.go b/pkg/handlers/request/context.go index ffbc0a0..b5a83b4 100644 --- a/pkg/handlers/request/context.go +++ b/pkg/handlers/request/context.go @@ -1,89 +1,25 @@ package request -import ( - "fmt" - "github.com/softwareplace/http-utils/api_context" - "github.com/softwareplace/wireguard-api/pkg/models" -) +type UserPrincipal struct { + Username string + Email string + Salt string + Roles []string + Status string +} type ApiContext struct { - User *models.User + User *UserPrincipal AccessId string ApiKeyId string AuthorizationClaims map[string]interface{} ApiKeyClaims map[string]interface{} } -func (ctx *ApiContext) Data(data api_context.ApiContextData) { - apiCtx := data.(*ApiContext) - ctx.User = apiCtx.User -} - -func (ctx *ApiContext) Salt() string { - if ctx.User != nil { - return ctx.User.Salt - - } - return "N/A" -} - -func (ctx *ApiContext) Roles() []string { - if ctx.User != nil { - return ctx.User.Roles - } - return []string{} -} - -func (ctx *ApiContext) GetAccessApiKeyId() string { - if ctx.ApiKeyId != "" { - return ctx.ApiKeyId - } - return "N/A" -} - -func (ctx *ApiContext) GetAccessId() string { - if ctx.AccessId != "" { - return ctx.AccessId - } - return "N/A" -} - -func (ctx *ApiContext) new() *ApiContext { - return &ApiContext{} -} - -func (ctx *ApiContext) GetRoles() (roles []string, err error) { - user := ctx.User - if user != nil && len(user.Roles) > 0 { - return user.Roles, nil - } - return nil, fmt.Errorf("user roles not found") -} - -func (ctx *ApiContext) SetUser(user *models.User) { - ctx.User = user -} - -func (ctx *ApiContext) SetAuthorizationClaims(authorizationClaims map[string]interface{}) { - ctx.AuthorizationClaims = authorizationClaims -} - -func (ctx *ApiContext) SetApiKeyClaims(apiKeyClaims map[string]interface{}) { - ctx.ApiKeyClaims = apiKeyClaims -} - -func (ctx *ApiContext) SetApiKeyId(apiKeyId string) { - ctx.ApiKeyId = apiKeyId -} - -func (ctx *ApiContext) SetAccessId(accessId string) { - ctx.AccessId = accessId -} - -func (ctx *ApiContext) SetRoles([]string) { +func (ctx *ApiContext) GetSalt() string { + return ctx.User.Salt } -func ContextBuilder(ctx *api_context.ApiRequestContext[*ApiContext]) bool { - ctx.RequestData = &ApiContext{} - return true +func (ctx *ApiContext) GetRoles() []string { + return ctx.User.Roles } diff --git a/pkg/handlers/user/create.go b/pkg/handlers/user/create.go index 486c3e7..f3ae985 100644 --- a/pkg/handlers/user/create.go +++ b/pkg/handlers/user/create.go @@ -22,7 +22,7 @@ func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*req return } - _, err := h.UsersRepository().FindUserByUsernameOrEmail(user.Username, user.Email) + _, err := (*h.UsersRepository()).FindUserByUsernameOrEmail(user.Username, user.Email) if err == nil { log.Printf("[%s]:: username or email already exists: %v", ctx.GetSessionId(), err) @@ -42,7 +42,7 @@ func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*req user.Salt = salt user.Status = "ACTIVE" - if err := h.UsersRepository().Save(user); err != nil { + if err := (*h.UsersRepository()).Save(user); err != nil { log.Printf("[%s]:: error saving user to the database: %v", ctx.GetSessionId(), err) ctx.Error("Error saving user to the database", http.StatusInternalServerError) return diff --git a/pkg/handlers/user/handlers.go b/pkg/handlers/user/handlers.go index 61341cf..f3c52b2 100644 --- a/pkg/handlers/user/handlers.go +++ b/pkg/handlers/user/handlers.go @@ -6,7 +6,6 @@ import ( "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "github.com/softwareplace/wireguard-api/pkg/utils/env" ) type Handler interface { @@ -21,17 +20,12 @@ type Handler interface { type handlerImpl struct { } -func (h *handlerImpl) UsersRepository() user.UsersRepository { +func (h *handlerImpl) UsersRepository() *user.UsersRepository { return user.Repository() } -func (h *handlerImpl) ApiSecurityService() security.ApiSecurityService[*request.ApiContext] { - return security.GetApiSecurityService[*request.ApiContext](env.AppEnv().ApiSecretAuthorization) -} - func Init(api server.ApiRouterHandler[*request.ApiContext]) { handler := handlerImpl{} - api.PublicRouter(handler.Login, "login", "POST") api.Post(handler.CreateUser, "user", "POST", "resource:users:create:user") api.Put(handler.UpdateUser, "user/:id", "resource:users:update:user") api.Put(handler.UpdateUser, "user") diff --git a/pkg/handlers/user/login.go b/pkg/handlers/user/login.go deleted file mode 100644 index eb58ecb..0000000 --- a/pkg/handlers/user/login.go +++ /dev/null @@ -1,58 +0,0 @@ -package user - -import ( - "errors" - "github.com/softwareplace/http-utils/api_context" - "github.com/softwareplace/http-utils/server" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" - "time" - - "github.com/softwareplace/wireguard-api/pkg/models" - "github.com/softwareplace/wireguard-api/pkg/utils/sec" - "github.com/softwareplace/wireguard-api/pkg/utils/validator" - "go.mongodb.org/mongo-driver/mongo" - "log" -) - -func (h *handlerImpl) Login(ctx *api_context.ApiRequestContext[*request.ApiContext]) { - server.GetRequestBody(ctx, models.User{}, h.checkUserCredentials, server.FailedToLoadBody) -} - -func (h *handlerImpl) checkUserCredentials(ctx *api_context.ApiRequestContext[*request.ApiContext], userInput models.User) { - decrypt, err := sec.Decrypt(userInput.Password, []byte(sec.SampleEncryptKey)) - - if err != nil { - log.Printf("Failed to decrypt password: %v", err) - } else { - userInput.Password = decrypt - } - - // Validate userResponse credentials - userResponse, err := h.UsersRepository().FindUserByUsernameOrEmail(userInput.Username, userInput.Email) - if err != nil { - if errors.Is(err, mongo.ErrNoDocuments) { - ctx.Unauthorized() - return - } - ctx.InternalServerError("Internal Server Error") - log.Printf("[%s]:: find user by username or email failed: %v", ctx.GetSessionId(), err) - - return - } - - if !validator.CheckPassword(userInput.Password, userResponse.Password, userResponse.Salt) { - ctx.Unauthorized() - return - } - - ctx.RequestData.User = userResponse - // Generate JWT and respond - tokenData, err := h.ApiSecurityService().GenerateJWT(ctx.RequestData, time.Minute*10) - if err != nil { - log.Printf("[%s]:: generating new jwt failed: %v", ctx.GetSessionId(), err) - ctx.InternalServerError("Error generating token") - return - } - - ctx.Ok(tokenData) -} diff --git a/pkg/handlers/user/update.go b/pkg/handlers/user/update.go index 0bf7802..5ff0458 100644 --- a/pkg/handlers/user/update.go +++ b/pkg/handlers/user/update.go @@ -15,7 +15,7 @@ func (h *handlerImpl) UpdateUser(ctx *api_context.ApiRequestContext[*request.Api } func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*request.ApiContext], updatedUser models.UserUpdate) { - currentUser, err := h.UsersRepository().FindUserBySalt(ctx.RequestData.AccessId) + currentUser, err := (*h.UsersRepository()).FindUserBySalt(ctx.AccessId) if err != nil { log.Printf("[%s]:: find user by salt failed: %v", ctx.GetSessionId(), err) @@ -37,7 +37,7 @@ func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*re } // Save updated user to database - err = h.UsersRepository().Update(*currentUser) + err = (*h.UsersRepository()).Update(*currentUser) if err != nil { log.Printf("[%s]:: updateing user failed: %v", ctx.GetSessionId(), err) diff --git a/pkg/handlers/user/user_handler/access_handler.go b/pkg/handlers/user/user_handler/access_handler.go deleted file mode 100644 index dbea410..0000000 --- a/pkg/handlers/user/user_handler/access_handler.go +++ /dev/null @@ -1,26 +0,0 @@ -package user_handler - -import ( - "github.com/softwareplace/http-utils/api_context" - "github.com/softwareplace/wireguard-api/pkg/domain/service/user_service" - "github.com/softwareplace/wireguard-api/pkg/handlers/request" -) - -type AuthenticationUserHandler interface { - Handler(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool -} - -type _AuthenticationUserHandlerImpl struct { - service *user_service.Service -} - -func GetAuthenticationUserHandler(service *user_service.Service) AuthenticationUserHandler { - return &_AuthenticationUserHandlerImpl{ - service: service, - } -} -func (h *_AuthenticationUserHandlerImpl) Handler(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool { - rolesLoader := (*h.service).LoadUserRoles - ctx.AccessRolesLoader = &rolesLoader - return true -} diff --git a/pkg/models/user.go b/pkg/models/user.go index 9bcbba0..611309a 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -1,6 +1,7 @@ package models import ( + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -22,3 +23,13 @@ type UserUpdate struct { Password string `json:"password"` Email string `json:"email"` } + +func (user *User) Parse() *request.UserPrincipal { + return &request.UserPrincipal{ + Username: user.Username, + Email: user.Email, + Salt: user.Salt, + Roles: user.Roles, + Status: user.Status, + } +} From a0a42075b233b6d1fab89b09f318364e3c418cb6 Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Sat, 18 Jan 2025 19:21:26 -0300 Subject: [PATCH 8/9] Fix user lookup by replacing salt with access ID Updated the `LoadPrincipal` method to use `AccessId` instead of `Salt` for user lookups. This ensures accurate mapping of user credentials and resolves potential mismatches during authentication. --- .../service/userPrincipalService/user_principal_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/domain/service/userPrincipalService/user_principal_service.go b/pkg/domain/service/userPrincipalService/user_principal_service.go index b58e5bd..62f630a 100644 --- a/pkg/domain/service/userPrincipalService/user_principal_service.go +++ b/pkg/domain/service/userPrincipalService/user_principal_service.go @@ -27,7 +27,7 @@ func New() principal.PService[*request.ApiContext] { } func (u *UserPrincipalService) LoadPrincipal(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool { - userResponse, err := u.userRepository.FindUserBySalt(ctx.Principal.GetSalt()) + userResponse, err := u.userRepository.FindUserBySalt(ctx.AccessId) if err != nil { return false } From 674f1bc1860353db47f5ef297d73250e0d09f81d Mon Sep 17 00:00:00 2001 From: Elias Miereles Date: Sun, 19 Jan 2025 19:22:44 -0300 Subject: [PATCH 9/9] Implement singleton pattern across services and repositories Refactored various services and repositories to use singletons for consistent initialization and to ensure thread safety. Updated dependencies, adjusted initialization flows, and revised method calls to align with the new singleton approach. --- Dockerfile | 1 + Makefile | 2 +- cmd/generator/api_secret/main.go | 9 ++-- cmd/server/main.go | 33 ++++++++---- dev/test.sh | 53 +++++++++++++++++++ go.mod | 2 +- go.sum | 18 +++---- pkg/domain/db/db.go | 13 ++--- .../repository/user/users_repository.go | 12 ++--- .../service/apiSecretService/service.go | 27 ++++++---- pkg/domain/service/peer/service.go | 11 +++- .../user_principal_service.go | 20 ++++--- .../service/user_service/login_service.go | 24 ++++++--- pkg/domain/service/user_service/service.go | 23 +++++--- pkg/handlers/request/context.go | 6 +++ pkg/handlers/user/create.go | 4 +- pkg/handlers/user/handlers.go | 2 +- pkg/handlers/user/update.go | 4 +- pkg/utils/env/envs.go | 43 ++++++++------- 19 files changed, 210 insertions(+), 97 deletions(-) create mode 100644 dev/test.sh diff --git a/Dockerfile b/Dockerfile index 32f5078..40edbe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ ARG PORT ENV PORT=$PORT # Copy the built app from the builder stage +COPY ./.temp/wireguard-tools /bin/wireguard-api COPY ./.temp/wireguard-api /bin/wireguard-api COPY ./.temp/api-key-generator /bin/api-key-generator diff --git a/Makefile b/Makefile index 5277f4b..a4e3d73 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ ENV = ./dev/.env # Start all services in detached mode up: - @$(DOCKER_COMPOSE) --env-file $(ENV) --project-name private-network -f $(DOCKER_COMPOSE_FILE) up -d + @$(DOCKER_COMPOSE) --env-file $(ENV) --project-name private-network -f $(DOCKER_COMPOSE_FILE) up --build -d @echo "Docker services are now running." @make logs diff --git a/cmd/generator/api_secret/main.go b/cmd/generator/api_secret/main.go index cdc1c25..0e28b3f 100644 --- a/cmd/generator/api_secret/main.go +++ b/cmd/generator/api_secret/main.go @@ -7,10 +7,11 @@ import ( "encoding/pem" "flag" "github.com/atotto/clipboard" - "github.com/softwareplace/http-utils/api_context" "github.com/softwareplace/http-utils/security" "github.com/softwareplace/wireguard-api/pkg/domain/db" "github.com/softwareplace/wireguard-api/pkg/domain/repository/api_secret" + "github.com/softwareplace/wireguard-api/pkg/domain/service/userPrincipalService" + "github.com/softwareplace/wireguard-api/pkg/handlers/request" "github.com/softwareplace/wireguard-api/pkg/models" "github.com/softwareplace/wireguard-api/pkg/utils/env" "log" @@ -98,7 +99,9 @@ func main() { Bytes: publicKeyBytes, }) - encryptedKey, err := security.GetApiSecurityService[*api_context.DefaultContext](appEnv.ApiSecretAuthorization).Encrypt(string(publicKeyPEM)) + principalService := userPrincipalService.GetUserPrincipalService() + securityService := security.ApiSecurityServiceBuild[*request.ApiContext](appEnv.ApiSecretAuthorization, principalService) + encryptedKey, err := securityService.Encrypt(string(publicKeyPEM)) if err != nil { log.Fatalf("Failed to sec public key: %s", err) @@ -127,7 +130,7 @@ func main() { Key: *id, } - apiSecretJWT, err := security.GetApiSecurityService[*api_context.DefaultContext](appEnv.ApiSecretAuthorization).GenerateApiSecretJWT(apiJWTInfo) + apiSecretJWT, err := securityService.GenerateApiSecretJWT(apiJWTInfo) if err != nil { return diff --git a/cmd/server/main.go b/cmd/server/main.go index ece75a4..ba2841b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -13,30 +13,41 @@ import ( "github.com/softwareplace/wireguard-api/pkg/utils/env" ) -func main() { - appEnv := env.AppEnv() - db.InitMongoDB() +var ( + userService user_service.Service + securityService security.ApiSecurityService[*request.ApiContext] + secreteAccessHandler security.ApiSecretAccessHandler[*request.ApiContext] + userLoginService server.LoginService[*request.ApiContext] +) - userService := user_service.GetService() - principalService := userPrincipalService.New() - secretService := apiSecretService.GetService() +func factory(appEnv env.ApplicationEnv) { + userService = user_service.GetService() + secretKeyProvider := apiSecretService.GetSecretKeyProvider() + principalService := userPrincipalService.GetUserPrincipalService() + securityService = security.ApiSecurityServiceBuild(appEnv.ApiSecretAuthorization, principalService) - securityService := security.ApiSecurityServiceBuild(appEnv.ApiSecretAuthorization, &principalService) - secreteAccessHandler := security.ApiSecretAccessHandlerBuild( + secreteAccessHandler = security.ApiSecretAccessHandlerBuild( appEnv.ApiSecretKey, - secretService.GetKey, + secretKeyProvider, securityService, ) + userLoginService = user_service.GetLoginService(securityService) +} - userLoginService := user_service.New(securityService) +func main() { + appEnv := env.AppEnv() + db.InitMongoDB() + + factory(appEnv) api := server.CreateApiRouter[*request.ApiContext](). RegisterMiddleware(secreteAccessHandler.HandlerSecretAccess, security.ApiSecretAccessHandlerName). RegisterMiddleware(securityService.AuthorizationHandler, security.ApiSecurityHandlerName). - WithLoginResource(&userLoginService) + WithLoginResource(userLoginService) handlers.Init(api) userService.Init() peer.GetService().Load() + api.StartServer() } diff --git a/dev/test.sh b/dev/test.sh new file mode 100644 index 0000000..e387be0 --- /dev/null +++ b/dev/test.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +mkdir .out + +# Number of iterations +COUNT=1000000 + +# The curl command +URL='http://localhost:1080/api/private-network/v1/login' +API_KEY='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlLZXkiOiJFQnFtMGgxS2o0M2RxSC8rSG05L2hEZjFBelpxdFhOeUpZRVhwMDZ0YzRjanZ0RFU2cU40eXc9PSIsImNsaWVudCI6IlNvZnR3YXJlIFBsYWNlIENPIiwiZXhwIjoyMDUyNzM3ODM1fQ.PaC_hYLbGMjv9ANJO1Ch09ul0nrMUkGnXM28Z1iLLr0' +USERNAME='my-username' +PASSWORD='ynT9558iiMga&ayTVGs3Gc6ug1' + + +response=$(curl --silent --location --write-out "%{http_code}" --output ./.out/response.json "$URL" \ + --header "X-Api-Key: $API_KEY" \ + --header "Content-Type: application/json" \ + --data "{ + \"username\": \"$USERNAME\", + \"password\": \"$PASSWORD\" + }") + + +makePeersRequest() { + local AUTHORIZATION + AUTHORIZATION=$1 + local PEERS_URL + PEERS_URL='http://localhost:1080/api/private-network/v1/peers' + + for i in $(seq 1 $COUNT); do + echo "Request #$i" + curl --silent --output ./.out/response.json --location "$PEERS_URL?key=$i" \ + --header "X-Api-Key: $API_KEY" \ + --header "Authorization: $AUTHORIZATION" \ + --header "Content-Type: application/json" + + cat ./.out/response.json || jq || true + done +} + +if [ "$response" -eq 200 ]; then + token=$(jq -r '.token' ./.out/response.json) + makePeersRequest "$token" +else + echo "Request failed with status code: $response" +fi +# +#for i in $(seq 1 $COUNT); do +# echo "Request #$i" +# +#done + +echo "Done sending $COUNT requests." diff --git a/go.mod b/go.mod index 52fe53b..203590f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.23.4 require ( github.com/atotto/clipboard v0.1.4 github.com/google/uuid v1.6.0 - github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b + github.com/softwareplace/http-utils v0.0.0-20250119222044-a46592dfd464 go.mongodb.org/mongo-driver v1.17.2 golang.org/x/crypto v0.32.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index f20fbef..c134bbd 100644 --- a/go.sum +++ b/go.sum @@ -16,18 +16,16 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676 h1:pTZv6az0E/CQBbVXUROyWQjdaFyamMMkz2YYvRf9CmI= -github.com/softwareplace/http-utils v0.0.0-20250118003817-f2b4ec9b8676/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= -github.com/softwareplace/http-utils v0.0.0-20250118120434-879af96930a9 h1:WPyYuPDF0CTXFLXiQ8OPRX9qeYUMqcJqHXq/hUFbrWc= -github.com/softwareplace/http-utils v0.0.0-20250118120434-879af96930a9/go.mod h1:5o/vgMsC67X2DChcdR76qm8yyjkIKmSFHudNeaK060U= -github.com/softwareplace/http-utils v0.0.0-20250118162347-a3eeb79c28ea h1:YtZQmFVbLSLy+lnaKvYJXk+ftQXESKhKq+QNy2FEJsU= -github.com/softwareplace/http-utils v0.0.0-20250118162347-a3eeb79c28ea/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= -github.com/softwareplace/http-utils v0.0.0-20250118203853-db4f95982ab9 h1:bVODWNdlh6nG2hdIwgzZS5KAFRDr4RA9YEn7Ol4vQbg= -github.com/softwareplace/http-utils v0.0.0-20250118203853-db4f95982ab9/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= -github.com/softwareplace/http-utils v0.0.0-20250118210746-bc0ebc16842b h1:dLH6T4tSO35Penou61Re1c8z1eJxBleTzcCh5aQgD1Y= -github.com/softwareplace/http-utils v0.0.0-20250118210746-bc0ebc16842b/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b h1:v4r1YBVnRtvn/umb2jsZ+ETwo5vd7a07b/rbxi2qrDQ= github.com/softwareplace/http-utils v0.0.0-20250118214521-e0c79d734a9b/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250119193252-ad2358849935 h1:K4MwOi5kfI1OK5nfYvR+AYTOmi/6WX4a6xgP7Zj53U8= +github.com/softwareplace/http-utils v0.0.0-20250119193252-ad2358849935/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250119205956-21b32f554f09 h1:WnVR8eszlyqlCIM1xLgafB/UoDn5BKsPtQS5XI9aEZY= +github.com/softwareplace/http-utils v0.0.0-20250119205956-21b32f554f09/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250119211455-787e57a219f1 h1:aUhJM9lgw3vNxoIxo7M+D0Z0ZcheRKdy6iWYBjkGvEE= +github.com/softwareplace/http-utils v0.0.0-20250119211455-787e57a219f1/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= +github.com/softwareplace/http-utils v0.0.0-20250119222044-a46592dfd464 h1:d/Zjb1DwmIxej6sanKe0sg6WY+hzk9ZQO/73tOP8TbU= +github.com/softwareplace/http-utils v0.0.0-20250119222044-a46592dfd464/go.mod h1:rMDg/RflN3SzoboxvRA1EqcJE897mduCdKvfHCNKsCg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= diff --git a/pkg/domain/db/db.go b/pkg/domain/db/db.go index 0480151..2293bea 100644 --- a/pkg/domain/db/db.go +++ b/pkg/domain/db/db.go @@ -10,16 +10,17 @@ import ( ) var ( - dbEnv env.DBEnv - once sync.Once - db *mongo.Database + dbEnv env.DBEnv + mongoDbOnce sync.Once + mongoDbInstance *mongo.Database ) func GetDB() *mongo.Database { - once.Do(func() { - db = GetDBClient().Database(dbEnv.DatabaseName) + dbEnv = env.AppEnv().DBEnv + mongoDbOnce.Do(func() { + mongoDbInstance = GetDBClient().Database(dbEnv.DatabaseName) }) - return db + return mongoDbInstance } func InitMongoDB() { diff --git a/pkg/domain/repository/user/users_repository.go b/pkg/domain/repository/user/users_repository.go index 59a761e..6a82bc3 100644 --- a/pkg/domain/repository/user/users_repository.go +++ b/pkg/domain/repository/user/users_repository.go @@ -25,15 +25,15 @@ func (r *usersRepositoryImpl) collection() *mongo.Collection { } var ( - repository UsersRepository - once sync.Once + repositoryInstance UsersRepository + repositoryOnce sync.Once ) -func Repository() *UsersRepository { - once.Do(func() { - repository = &usersRepositoryImpl{ +func Repository() UsersRepository { + repositoryOnce.Do(func() { + repositoryInstance = &usersRepositoryImpl{ database: db.GetDB(), } }) - return &repository + return repositoryInstance } diff --git a/pkg/domain/service/apiSecretService/service.go b/pkg/domain/service/apiSecretService/service.go index e597282..be95493 100644 --- a/pkg/domain/service/apiSecretService/service.go +++ b/pkg/domain/service/apiSecretService/service.go @@ -2,25 +2,32 @@ package apiSecretService import ( "github.com/softwareplace/http-utils/api_context" + "github.com/softwareplace/http-utils/security" "github.com/softwareplace/wireguard-api/pkg/domain/repository/api_secret" "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "sync" ) -type ApiSecretService interface { - GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) -} - -type serviceImpl struct { +type apiSecretKeyProviderImpl struct { repository api_secret.ApiSecretRepository } -func GetService() ApiSecretService { - return &serviceImpl{ - repository: api_secret.GetRepository(), - } +var ( + apiSecretKeyProviderOnce sync.Once + apiSecretKeyProviderInstance security.ApiSecretKeyProvider[*request.ApiContext] +) + +func GetSecretKeyProvider() security.ApiSecretKeyProvider[*request.ApiContext] { + apiSecretKeyProviderOnce.Do(func() { + apiSecretKeyProviderInstance = &apiSecretKeyProviderImpl{ + repository: api_secret.GetRepository(), + } + }) + + return apiSecretKeyProviderInstance } -func (s *serviceImpl) GetKey(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) { +func (s *apiSecretKeyProviderImpl) Get(ctx *api_context.ApiRequestContext[*request.ApiContext]) (string, error) { apiSecret, err := s.repository.GetById(ctx.ApiKeyId) if err != nil { return "", err diff --git a/pkg/domain/service/peer/service.go b/pkg/domain/service/peer/service.go index 5d8c725..296de3d 100644 --- a/pkg/domain/service/peer/service.go +++ b/pkg/domain/service/peer/service.go @@ -3,6 +3,7 @@ package peer import ( "github.com/softwareplace/wireguard-api/pkg/domain/repository/peer" "github.com/softwareplace/wireguard-api/pkg/models" + "sync" ) type Service interface { @@ -19,6 +20,14 @@ func (s *serviceImpl) repository() peer.Repository { return peer.GetRepository() } +var ( + serviceInstance Service + serviceOnce sync.Once +) + func GetService() Service { - return &serviceImpl{} + serviceOnce.Do(func() { + serviceInstance = &serviceImpl{} + }) + return serviceInstance } diff --git a/pkg/domain/service/userPrincipalService/user_principal_service.go b/pkg/domain/service/userPrincipalService/user_principal_service.go index 62f630a..47ea253 100644 --- a/pkg/domain/service/userPrincipalService/user_principal_service.go +++ b/pkg/domain/service/userPrincipalService/user_principal_service.go @@ -13,17 +13,17 @@ type UserPrincipalService struct { } var ( - once sync.Once - service principal.PService[*request.ApiContext] + userPrincipalServiceOnce sync.Once + userPrincipalServiceInstance principal.PService[*request.ApiContext] ) -func New() principal.PService[*request.ApiContext] { - once.Do(func() { - service = &UserPrincipalService{ - userRepository: *user.Repository(), +func GetUserPrincipalService() principal.PService[*request.ApiContext] { + userPrincipalServiceOnce.Do(func() { + userPrincipalServiceInstance = &UserPrincipalService{ + userRepository: user.Repository(), } }) - return service + return userPrincipalServiceInstance } func (u *UserPrincipalService) LoadPrincipal(ctx *api_context.ApiRequestContext[*request.ApiContext]) bool { @@ -32,9 +32,7 @@ func (u *UserPrincipalService) LoadPrincipal(ctx *api_context.ApiRequestContext[ return false } - ctx.Principal = &request.ApiContext{ - User: userResponse.Parse(), - } - + context := request.NewApiContext(userResponse.Parse()) + ctx.Principal = &context return true } diff --git a/pkg/domain/service/user_service/login_service.go b/pkg/domain/service/user_service/login_service.go index 184e55a..6aefb8d 100644 --- a/pkg/domain/service/user_service/login_service.go +++ b/pkg/domain/service/user_service/login_service.go @@ -5,27 +5,37 @@ import ( "github.com/softwareplace/http-utils/server" "github.com/softwareplace/wireguard-api/pkg/domain/repository/user" "github.com/softwareplace/wireguard-api/pkg/handlers/request" + "sync" "time" ) type loginServiceImpl struct { securityService security.ApiSecurityService[*request.ApiContext] - repository *user.UsersRepository + repository user.UsersRepository } func (l *loginServiceImpl) SecurityService() security.ApiSecurityService[*request.ApiContext] { return l.securityService } -func New(securityService security.ApiSecurityService[*request.ApiContext]) server.LoginService[*request.ApiContext] { - return &loginServiceImpl{ - securityService: securityService, - repository: user.Repository(), - } +var ( + loginServiceOnce sync.Once + loginServiceInstance server.LoginService[*request.ApiContext] +) + +func GetLoginService(securityService security.ApiSecurityService[*request.ApiContext]) server.LoginService[*request.ApiContext] { + loginServiceOnce.Do(func() { + loginServiceInstance = &loginServiceImpl{ + securityService: securityService, + repository: user.Repository(), + } + }) + + return loginServiceInstance } func (l *loginServiceImpl) Login(user server.LoginEntryData) (*request.ApiContext, error) { - response, err := (*l.repository).FindUserByUsernameOrEmail(user.Username, user.Email) + response, err := l.repository.FindUserByUsernameOrEmail(user.Username, user.Email) if err != nil { return nil, err } diff --git a/pkg/domain/service/user_service/service.go b/pkg/domain/service/user_service/service.go index 20aa1d3..4de19b3 100644 --- a/pkg/domain/service/user_service/service.go +++ b/pkg/domain/service/user_service/service.go @@ -8,6 +8,7 @@ import ( "github.com/softwareplace/wireguard-api/pkg/utils/sec" "github.com/softwareplace/wireguard-api/pkg/utils/validator" "log" + "sync" ) type Service interface { @@ -16,14 +17,22 @@ type Service interface { type serviceImpl struct { appEnv env.ApplicationEnv - repository *repo.UsersRepository + repository repo.UsersRepository } +var ( + serviceOnce sync.Once + serviceInstance Service +) + func GetService() Service { - return &serviceImpl{ - appEnv: env.AppEnv(), - repository: repo.Repository(), - } + serviceOnce.Do(func() { + serviceInstance = &serviceImpl{ + appEnv: env.AppEnv(), + repository: repo.Repository(), + } + }) + return serviceInstance } type userInit struct { @@ -43,7 +52,7 @@ func (s *serviceImpl) Init() { log.Fatalf("Failed to validate init user data: %v", err) } - _, err = (*s.repository).FindUserByUsernameOrEmail(initUserData.Username, initUserData.Email) + _, err = s.repository.FindUserByUsernameOrEmail(initUserData.Username, initUserData.Email) if err == nil { return @@ -59,7 +68,7 @@ func (s *serviceImpl) Init() { initUserData.Salt = salt initUserData.Status = "ACTIVE" - err = (*s.repository).Save(initUserData) + err = s.repository.Save(initUserData) if err != nil { log.Fatalf("Failed to save init user data: %v", err) diff --git a/pkg/handlers/request/context.go b/pkg/handlers/request/context.go index b5a83b4..cdd8ede 100644 --- a/pkg/handlers/request/context.go +++ b/pkg/handlers/request/context.go @@ -16,6 +16,12 @@ type ApiContext struct { ApiKeyClaims map[string]interface{} } +func NewApiContext(user *UserPrincipal) *ApiContext { + return &ApiContext{ + User: user, + } +} + func (ctx *ApiContext) GetSalt() string { return ctx.User.Salt } diff --git a/pkg/handlers/user/create.go b/pkg/handlers/user/create.go index f3ae985..486c3e7 100644 --- a/pkg/handlers/user/create.go +++ b/pkg/handlers/user/create.go @@ -22,7 +22,7 @@ func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*req return } - _, err := (*h.UsersRepository()).FindUserByUsernameOrEmail(user.Username, user.Email) + _, err := h.UsersRepository().FindUserByUsernameOrEmail(user.Username, user.Email) if err == nil { log.Printf("[%s]:: username or email already exists: %v", ctx.GetSessionId(), err) @@ -42,7 +42,7 @@ func (h *handlerImpl) validateUserFields(ctx *api_context.ApiRequestContext[*req user.Salt = salt user.Status = "ACTIVE" - if err := (*h.UsersRepository()).Save(user); err != nil { + if err := h.UsersRepository().Save(user); err != nil { log.Printf("[%s]:: error saving user to the database: %v", ctx.GetSessionId(), err) ctx.Error("Error saving user to the database", http.StatusInternalServerError) return diff --git a/pkg/handlers/user/handlers.go b/pkg/handlers/user/handlers.go index f3c52b2..8547fc0 100644 --- a/pkg/handlers/user/handlers.go +++ b/pkg/handlers/user/handlers.go @@ -20,7 +20,7 @@ type Handler interface { type handlerImpl struct { } -func (h *handlerImpl) UsersRepository() *user.UsersRepository { +func (h *handlerImpl) UsersRepository() user.UsersRepository { return user.Repository() } diff --git a/pkg/handlers/user/update.go b/pkg/handlers/user/update.go index 5ff0458..f993d04 100644 --- a/pkg/handlers/user/update.go +++ b/pkg/handlers/user/update.go @@ -15,7 +15,7 @@ func (h *handlerImpl) UpdateUser(ctx *api_context.ApiRequestContext[*request.Api } func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*request.ApiContext], updatedUser models.UserUpdate) { - currentUser, err := (*h.UsersRepository()).FindUserBySalt(ctx.AccessId) + currentUser, err := h.UsersRepository().FindUserBySalt(ctx.AccessId) if err != nil { log.Printf("[%s]:: find user by salt failed: %v", ctx.GetSessionId(), err) @@ -37,7 +37,7 @@ func (h *handlerImpl) useUpdateValidation(ctx *api_context.ApiRequestContext[*re } // Save updated user to database - err = (*h.UsersRepository()).Update(*currentUser) + err = h.UsersRepository().Update(*currentUser) if err != nil { log.Printf("[%s]:: updateing user failed: %v", ctx.GetSessionId(), err) diff --git a/pkg/utils/env/envs.go b/pkg/utils/env/envs.go index d2cf209..d04ffb2 100644 --- a/pkg/utils/env/envs.go +++ b/pkg/utils/env/envs.go @@ -3,6 +3,7 @@ package env import ( "log" "os" + "sync" ) type ApplicationEnv struct { @@ -32,33 +33,39 @@ type DBEnv struct { Uri string } -var appEnv *ApplicationEnv +var ( + instance *ApplicationEnv + appEnvOnce sync.Once +) func AppEnv() ApplicationEnv { if os.Getenv("DEBUG_MODE") == "true" { log.SetFlags(log.LstdFlags | log.Llongfile) } - if appEnv == nil { + appEnvOnce.Do(func() { + if instance == nil { - dbEnv := DBEnv{ - DatabaseName: GetRequiredEnv("MONGO_DATABASE"), // required - Username: GetRequiredEnv("MONGO_USERNAME"), // required - Password: GetRequiredEnv("MONGO_PASSWORD"), // required - Uri: GetRequiredEnv("MONGO_URI"), // required - } + dbEnv := DBEnv{ + DatabaseName: GetRequiredEnv("MONGO_DATABASE"), // required + Username: GetRequiredEnv("MONGO_USERNAME"), // required + Password: GetRequiredEnv("MONGO_PASSWORD"), // required + Uri: GetRequiredEnv("MONGO_URI"), // required + } - appEnv = &ApplicationEnv{ - ApiSecretAuthorization: GetRequiredEnv("API_SECRET_AUTHORIZATION"), // required - Port: getServerPort(), - ContextPath: getServerContextPath(), - PeerResourcePath: getPeerResourcePath(), - ApiSecretKey: GetRequiredEnv("API_SECRET_KEY"), - InitFilePath: os.Getenv("API_INIT_FILE"), - DBEnv: dbEnv, + instance = &ApplicationEnv{ + ApiSecretAuthorization: GetRequiredEnv("API_SECRET_AUTHORIZATION"), // required + Port: getServerPort(), + ContextPath: getServerContextPath(), + PeerResourcePath: getPeerResourcePath(), + ApiSecretKey: GetRequiredEnv("API_SECRET_KEY"), + InitFilePath: os.Getenv("API_INIT_FILE"), + DBEnv: dbEnv, + } } - } - return *appEnv + }) + + return *instance } func getPeerResourcePath() string {