diff --git a/Containerfile b/Containerfile index 303ad0d..49dcc94 100644 --- a/Containerfile +++ b/Containerfile @@ -10,4 +10,5 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o backend FROM scratch COPY --from=0 /app/backend /app COPY --from=0 /etc/ssl /etc/ssl +COPY migrations /migrations ENTRYPOINT ["/app"] diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..d9bfa02 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json + +version: "3" + +tasks: + build: + desc: Build go code + cmd: go build + silent: true + + run-migrations-dev: + desc: Execute database migrations + env: + SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable + cmd: go run main.go migrate --migrations-path=file://migrations + + run-server-dev: + desc: Run the local dev server + env: + SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable + SOFTPLAYER_REDIS_HOST: localhost:30379 + cmd: go run main.go serve --dev-mode --reflection + + deploy-local-env: + desc: Run a kind cluster and deploy deps + deps: + - kind-cluster + - helmfile-deploy + + kind-cluster: + desc: Run a kind cluster + cmd: kind create cluster --config ./kind-config.yaml + + kind-cluster-remove: + desc: Remove the kind cluster + cmd: kind delete cluster + + helmfile-deploy: + desc: Deploy the helmfile for the local dev + cmd: helmfile apply diff --git a/api/v1/accounts.go b/api/v1/accounts.go deleted file mode 100644 index 7dedad8..0000000 --- a/api/v1/accounts.go +++ /dev/null @@ -1,68 +0,0 @@ -package v1 - -import ( - "context" - "database/sql" - - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/email" - "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" - "github.com/golang/protobuf/ptypes/empty" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func NewAccountRPCImpl(db *sql.DB, hashCost int16, email *email.EmailConf, devMode bool) *AccountsServer { - return &AccountsServer{ - Params: &controllers.AccountParams{ - HashCost: hashCost, - }, - SQLDriver: db, - emailConfig: *email, - devMode: devMode, - } -} - -type AccountsServer struct { - accounts.UnimplementedAccountsServer - Params *controllers.AccountParams - SQLDriver *sql.DB - emailConfig email.EmailConf - // When dev mode is enabled, email won't be sent, instead the code will be returned in metadata - devMode bool -} - -func (a *AccountsServer) SignUp(ctx context.Context, in *accounts.AccountWithPassword) (*accounts.AccountFullWithToken, error) { - accountCtrl := controllers.AccountController{ - Params: controllers.AccountParams{ - HashCost: a.Params.HashCost, - }, - DB: a.SQLDriver, - DevMode: a.devMode, - } - - if err := accountCtrl.Create(ctx, &controllers.AccountData{ - Username: in.Data.GetName(), - Password: in.Password.GetPassword(), - Email: in.Data.GetEmail(), - }); err != nil { - return nil, status.Error(codes.Aborted, "Couldn't create a user") - } - return &accounts.AccountFullWithToken{ - Id: &accounts.AccountId{}, - Data: &accounts.AccountData{}, - Token: "", - }, nil -} - -func (a *AccountsServer) SignIn(ctx context.Context, in *accounts.AccountWithPassword) (*accounts.AccountFullWithToken, error) { - return nil, status.Error(codes.Unimplemented, "Endpoint is not implemented") -} - -func (a *AccountsServer) ResetPassword(ctx context.Context, in *accounts.AccountData) (*empty.Empty, error) { - return nil, status.Error(codes.Unimplemented, "Endpoint is not implemented") -} - -func (acc *AccountsServer) NewPassword(ctx context.Context, in *accounts.AccountWithPasswordAndCode) (*empty.Empty, error) { - return nil, status.Error(codes.Unimplemented, "Endpoint is not implemented") -} diff --git a/api/v1/accounts_auth.go b/api/v1/accounts_auth.go new file mode 100644 index 0000000..b3687c9 --- /dev/null +++ b/api/v1/accounts_auth.go @@ -0,0 +1,59 @@ +package v1 + +import ( + "context" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" + accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +func NewAccountAuthRPCImpl(ctrl *controllers.AccountController) *AccountsAuthServer { + return &AccountsAuthServer{ + ctrl: ctrl, + } +} + +type AccountsAuthServer struct { + accounts.UnimplementedAccountsAuthServiceServer + ctrl *controllers.AccountController +} + +func (a *AccountsAuthServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { + tokenID := ctx.Value("token_id").(string) + userID := ctx.Value("user_id").(string) + log := logger.FromContext(ctx) + uuid, err := a.ctrl.ValidateRefreshToken(ctx, tokenID, userID) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "refresh token is invalid") + } + accessToken, err := a.ctrl.GenerateAccessToken(uuid) + if err != nil { + log.Error(err, "Couldn't generate an access token") + return nil, status.Error(codes.Aborted, "Couldn't generate Access Token") + } + + refreshToken, err := a.ctrl.GenerateRefreshToken(ctx, uuid) + if err != nil { + log.Error(err, "Couldn't generate a refresh token") + return nil, status.Error(codes.Aborted, "Couldn't generate Access Token") + } + + header := metadata.Pairs( + "access-token", accessToken, + "refreshToken", refreshToken, + ) + + if err := grpc.SetHeader(ctx, header); err != nil { + log.Error(err, "Couldn't set headers") + return nil, status.Error(codes.Unknown, "Couldn't set headers") + } + + return &emptypb.Empty{}, nil +} diff --git a/api/v1/accounts_no_auth.go b/api/v1/accounts_no_auth.go new file mode 100644 index 0000000..d6aad9f --- /dev/null +++ b/api/v1/accounts_no_auth.go @@ -0,0 +1,73 @@ +package v1 + +import ( + "context" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" + accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/oauth2" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +func NewAccountNoAuthRPCImpl(ctrl *controllers.AccountController) *AccountsNoAuthServer { + return &AccountsNoAuthServer{ + ctrl: ctrl, + } +} + +type AccountsNoAuthServer struct { + accounts.UnimplementedAccountsNoAuthServiceServer + ctrl *controllers.AccountController +} + +func (a *AccountsNoAuthServer) SignIn(ctx context.Context, in *accounts.SignInRequest) (*empty.Empty, error) { + provider, err := oidc.NewProvider(ctx, "https://authentik.badhouseplants.net") + if err != nil { + return nil, err + } + + // Configure an OpenID Connect aware OAuth2 client. + oauth2Config := oauth2.Config{ + ClientID: "softplayer-localhost", + ClientSecret: "pRpe3scGUE2jNH6t5rqI9R4OROeQHs4eO6ku957mYjDumKhQGX8QJcO0BMJ2FG4sUpvFrqccEqWgc3wKMp94tC8LyvTnkPF0Tg0CaldAEHuoQQdNKAzXVxwrHE6kNyBC", + RedirectURL: "http://localhost:8080/#/auth/callback", + + // Discovery returns the OAuth2 endpoints. + Endpoint: provider.Endpoint(), + + // "openid" is a required scope for OpenID Connect flows. + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + } + verifier := provider.Verifier(&oidc.Config{ClientID: "softplayer-localhost"}) + + oauth2Token, err := oauth2Config.Exchange(ctx, in.Code) + if err != nil { + return nil, err + } + + // Extract the ID Token from OAuth2 token. + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + return nil, status.Error(codes.Unauthenticated, "Couldn't parse oauth token") + } + + // Parse and verify ID Token payload. + idToken, err := verifier.Verify(ctx, rawIDToken) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "Couldn't verify oauth token") + } + + // Extract custom claims + var claims struct { + Email string `json:"email"` + Verified bool `json:"email_verified"` + } + if err := idToken.Claims(&claims); err != nil { + // handle error + } + return &emptypb.Empty{}, nil +} diff --git a/api/v1/application.go b/api/v1/application.go deleted file mode 100644 index 9d46de5..0000000 --- a/api/v1/application.go +++ /dev/null @@ -1,84 +0,0 @@ -package v1 - -import ( - "context" - - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/applications/v1" - "github.com/go-logr/logr" - "github.com/golang/protobuf/ptypes/empty" - "google.golang.org/protobuf/types/known/emptypb" - ctrl "sigs.k8s.io/controller-runtime" -) - -func NewApplicationsGrpcImpl(controller ctrl.Manager, log logr.Logger) *ApplicationServer { - return &ApplicationServer{ - controller: controller, - logInstance: log, - } -} - -type ApplicationServer struct { - proto.UnimplementedApplicationsServer - controller ctrl.Manager - logInstance logr.Logger -} - -// Create an environment -func (app *ApplicationServer) Create(ctx context.Context, in *proto.CreateOptions) (*proto.ApplicationFull, error) { - log := app.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid(), "environment_id", in.GetSpec().GetEnvironemntId(), "app_name", in.GetSpec().GetApplication()) - ctx = logr.NewContext(ctx, log) - - data := &controllers.ApplicationData{ - Name: in.Metadata.Name, - Application: in.Spec.Application, - Version: in.Spec.Version, - Environemnt: in.Spec.EnvironemntId, - Config: in.Spec.Config, - RawConfig: "", - } - - application := &controllers.Application{ - UserID: in.GetOwnerId().GetUuid(), - Controller: app.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - - err := application.Create(ctx) - if err != nil { - return nil, err - } - return &proto.ApplicationFull{ - Metadata: in.GetMetadata(), - Id: &proto.ApplicationId{ - Uuid: application.Data.UUID, - }, - Spec: in.GetSpec(), - }, nil -} - -func (app *ApplicationServer) Delete(ctx context.Context, in *proto.DeleteOptions) (*empty.Empty, error) { - log := app.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid(), "app_id", in.GetId().GetUuid()) - ctx = logr.NewContext(ctx, log) - - data := &controllers.ApplicationData{ - Name: in.Metadata.Name, - UUID: in.GetId().GetUuid(), - } - - application := &controllers.Application{ - UserID: in.GetOwnerId().GetUuid(), - Controller: app.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - - err := application.Delete(ctx) - if err != nil { - return nil, err - } - return &emptypb.Empty{}, nil -} diff --git a/api/v1/email.go b/api/v1/email.go deleted file mode 100644 index ff01de9..0000000 --- a/api/v1/email.go +++ /dev/null @@ -1,86 +0,0 @@ -package v1 - -import ( - "context" - "fmt" - - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/email" - proto_email "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/email/v1" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" - ctrl "sigs.k8s.io/controller-runtime" -) - -type EmailServer struct { - proto_email.UnimplementedEmailValidationServer - emailConfig email.EmailConf - controller ctrl.Manager - // When dev mode is enabled, email won't be sent, instead the code will be returned in metadata - devMode bool -} - -func InitEmailServer(controller ctrl.Manager, emailConfig *email.EmailConf, devMode bool) *EmailServer { - return &EmailServer{ - controller: controller, - emailConfig: *emailConfig, - devMode: devMode, - } -} - -// Send the validation code to email -func (c *EmailServer) SendRequest(ctx context.Context, in *proto_email.RequestValidation) (*emptypb.Empty, error) { - // Validation - if len(in.GetUserId()) == 0 { - return nil, status.Error(codes.InvalidArgument, "user id must not be empty") - } - - // Body - emailSvc := controllers.EmailSvc{ - Data: controllers.EmailData{ - UserID: in.GetUserId(), - }, - EmailConfig: c.emailConfig, - Controller: c.controller, - DevMode: c.devMode, - } - err := emailSvc.SendVerification(ctx) - if err != nil { - return nil, err - } - if c.devMode { - header := metadata.Pairs("code", emailSvc.Data.Code) - if err := grpc.SendHeader(ctx, header); err != nil { - return nil, err - } - } - return &emptypb.Empty{}, nil -} - -func (c *EmailServer) ValidateEmail(ctx context.Context, in *proto_email.ConfirmValidation) (*emptypb.Empty, error) { - // Validation - if len(in.GetUserId()) == 0 { - return nil, status.Error(codes.InvalidArgument, "user id must not be empty") - } - if in.GetCode() == 0 { - return nil, status.Error(codes.InvalidArgument, "code must not be empty") - } - - // Body - emailSvc := controllers.EmailSvc{ - Data: controllers.EmailData{ - UserID: in.GetUserId(), - Code: fmt.Sprintf("%d", in.GetCode()), - }, - EmailConfig: c.emailConfig, - Controller: c.controller, - } - err := emailSvc.ConfirmVerification(ctx) - if err != nil { - return nil, err - } - return &emptypb.Empty{}, nil -} diff --git a/api/v1/environments.go b/api/v1/environments.go deleted file mode 100644 index b381ddf..0000000 --- a/api/v1/environments.go +++ /dev/null @@ -1,261 +0,0 @@ -package v1 - -import ( - "context" - - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/providers/infra" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/providers/kubernetes" - proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/environments/v1" - "github.com/go-logr/logr" - "github.com/golang/protobuf/ptypes/empty" - ctrl "sigs.k8s.io/controller-runtime" -) - -func NewapiGrpcImpl(controller ctrl.Manager, log logr.Logger) *EnvironmentsServer { - return &EnvironmentsServer{ - controller: controller, - logInstance: log, - } -} - -type EnvironmentsServer struct { - proto.UnimplementedEnvironmentsServer - controller ctrl.Manager - logInstance logr.Logger -} - -// Create an environment -func (e *EnvironmentsServer) Create(ctx context.Context, in *proto.CreateOptions) (*proto.EnvironmentFull, error) { - log := e.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid()) - ctx = logr.NewContext(ctx, log) - - provider, err := infra.GetProvider(in.GetSpec().GetProvider().String()) - if err != nil { - return nil, err - } - - k8s, err := kubernetes.GetKubernetes(in.GetSpec().GetKubernetes().String()) - if err != nil { - return nil, err - } - - location, err := provider.GetServerLocation(in.GetSpec().GetServerLocation().String()) - if err != nil { - return nil, err - } - - serverType, err := provider.GetServerType(in.Spec.ServerType.String()) - if err != nil { - return nil, err - } - - data := &controllers.EnvironemntData{ - Name: in.GetMetadata().GetName(), - Description: in.GetMetadata().GetDescription(), - Provider: provider.GetProviderName(), - Kubernetes: k8s.GetKubernetesName(), - Location: location, - ServerType: serverType, - DiskSize: int(in.GetSpec().GetDiskSize()), - } - - environment := &controllers.Environemnt{ - UserID: in.GetOwnerId().GetUuid(), - Config: ctrl.GetConfigOrDie(), - Controller: e.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - - err = environment.Create(ctx) - if err != nil { - return nil, err - } - return &proto.EnvironmentFull{ - Metadata: in.GetMetadata(), - Id: &proto.EnvironmentId{ - Uuid: environment.Data.UUID, - }, - Spec: in.GetSpec(), - }, nil -} - -func (e *EnvironmentsServer) Update(ctx context.Context, in *proto.UpdateOptions) (*proto.EnvironmentFull, error) { - log := e.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid()) - ctx = logr.NewContext(ctx, log) - - provider, err := infra.GetProvider(in.GetSpec().GetProvider().String()) - if err != nil { - return nil, err - } - - k8s, err := kubernetes.GetKubernetes(in.GetSpec().GetKubernetes().String()) - if err != nil { - return nil, err - } - - location, err := provider.GetServerLocation(in.GetSpec().GetServerLocation().String()) - if err != nil { - return nil, err - } - - serverType, err := provider.GetServerType(in.Spec.ServerType.String()) - if err != nil { - return nil, err - } - - data := &controllers.EnvironemntData{ - Name: in.GetMetadata().GetName(), - UUID: in.GetId().GetUuid(), - Description: in.GetMetadata().GetDescription(), - Provider: provider.GetProviderName(), - Kubernetes: k8s.GetKubernetesName(), - Location: location, - ServerType: serverType, - DiskSize: int(in.GetSpec().GetDiskSize()), - } - - environment := &controllers.Environemnt{ - Config: ctrl.GetConfigOrDie(), - UserID: in.GetOwnerId().GetUuid(), - Controller: e.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - - err = environment.Update(ctx) - if err != nil { - return nil, err - } - - return &proto.EnvironmentFull{ - Metadata: in.GetMetadata(), - Id: in.GetId(), - Spec: in.GetSpec(), - }, nil - -} - -func (e *EnvironmentsServer) Delete(ctx context.Context, in *proto.DeleteOptions) (*empty.Empty, error) { - log := e.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid()) - ctx = logr.NewContext(ctx, log) - - data := &controllers.EnvironemntData{ - Name: in.GetMetadata().GetName(), - UUID: in.GetId().GetUuid(), - } - - environment := &controllers.Environemnt{ - Config: ctrl.GetConfigOrDie(), - UserID: in.GetOwnerId().GetUuid(), - Controller: e.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - err := environment.Delete(ctx) - if err != nil { - return nil, err - } - return nil, nil -} - -func (e *EnvironmentsServer) Get(ctx context.Context, in *proto.GetOptions) (*proto.EnvironmentFull, error) { - log := e.logInstance - log.WithValues("user_id", in.GetOwnerId().GetUuid()) - ctx = logr.NewContext(ctx, log) - - data := &controllers.EnvironemntData{ - UUID: in.GetId().GetUuid(), - } - - environment := &controllers.Environemnt{ - Config: ctrl.GetConfigOrDie(), - UserID: in.GetOwnerId().GetUuid(), - Controller: e.controller, - Data: data, - Token: in.GetToken().GetToken(), - } - - if err := environment.Get(ctx); err != nil { - return nil, err - } - - provider, err := infra.GetProvider(environment.Data.Provider) - if err != nil { - return nil, err - } - - k8s, err := kubernetes.GetKubernetes(environment.Data.Kubernetes) - if err != nil { - return nil, err - } - - return &proto.EnvironmentFull{ - Spec: &proto.EnvironmentSpec{ - Provider: proto.Provider(proto.Provider_value[provider.RawProviderName()]), - Kubernetes: proto.Kubernetes(proto.Kubernetes_value[k8s.RawKubernetesName()]), - ServerLocation: proto.Location(proto.Location_value[provider.RawServerLocation(environment.Data.Location)]), - ServerType: proto.ServerType(proto.ServerType_value[provider.RawServerType(environment.Data.ServerType)]), - DiskSize: int32(environment.Data.DiskSize), - }, - Id: in.GetId(), - Metadata: &proto.EnvironmentMetadata{ - Name: environment.Data.Name, - Description: environment.Data.Description, - }, - }, nil -} - -func (e *EnvironmentsServer) List(in *proto.ListOptions, stream proto.Environments_ListServer) error { - logInstance := e.logInstance - log := logInstance.WithValues("user_id", in.GetOwnerId().GetUuid()) - ctx := logr.NewContext(stream.Context(), log) - - environment := &controllers.Environemnt{ - Config: ctrl.GetConfigOrDie(), - UserID: in.GetOwnerId().GetUuid(), - Controller: e.controller, - Token: in.GetToken().GetToken(), - } - - envs, err := environment.List(ctx, in.GetSearchString()) - if err != nil { - return err - } - - for _, env := range envs { - provider, err := infra.GetProvider(env.Data.Provider) - if err != nil { - return err - } - - k8s, err := kubernetes.GetKubernetes(env.Data.Kubernetes) - if err != nil { - return err - } - - if err := stream.Send(&proto.EnvironmentFull{ - Metadata: &proto.EnvironmentMetadata{ - Name: env.Data.Name, - Description: env.Data.Description, - }, - Id: &proto.EnvironmentId{ - Uuid: env.Data.UUID, - }, - Spec: &proto.EnvironmentSpec{ - Provider: proto.Provider(proto.Provider_value[provider.RawProviderName()]), - Kubernetes: proto.Kubernetes(proto.Kubernetes_value[k8s.RawKubernetesName()]), - ServerLocation: proto.Location(proto.Location_value[provider.RawServerLocation(env.Data.Location)]), - ServerType: proto.ServerType(proto.ServerType_value[provider.RawServerType(env.Data.ServerType)]), - DiskSize: int32(env.Data.DiskSize), - }, - }); err != nil { - return err - } - } - return nil -} diff --git a/api/v1/test_auth.go b/api/v1/test_auth.go new file mode 100644 index 0000000..692b3de --- /dev/null +++ b/api/v1/test_auth.go @@ -0,0 +1,22 @@ +package v1 + +import ( + "context" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" + test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" +) + +func NewTestAuthRPCImpl() *TestAuthServer { + return &TestAuthServer{} +} + +type TestAuthServer struct { + test.UnimplementedTestAuthServiceServer +} + +func (t *TestAuthServer) Pong(ctx context.Context, in *test.PongRequest) (*test.PongResponse, error) { + log := logger.FromContext(ctx) + log.Info("Pong") + return &test.PongResponse{}, nil +} diff --git a/api/v1/test_no_auth.go b/api/v1/test_no_auth.go new file mode 100644 index 0000000..40bbd09 --- /dev/null +++ b/api/v1/test_no_auth.go @@ -0,0 +1,22 @@ +package v1 + +import ( + "context" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" + test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" +) + +func NewTestNoAuthRPCImpl() *TestNoAuthServer { + return &TestNoAuthServer{} +} + +type TestNoAuthServer struct { + test.UnimplementedTestNoAuthServiceServer +} + +func (t *TestNoAuthServer) Ping(ctx context.Context, in *test.PingRequest) (*test.PingResponse, error) { + log := logger.FromContext(ctx) + log.Info("Ping") + return &test.PingResponse{}, nil +} diff --git a/go.mod b/go.mod index 78e66c3..80ad433 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,22 @@ go 1.25.9 require ( github.com/alecthomas/assert/v2 v2.11.0 github.com/alecthomas/kong v1.15.0 + github.com/coreos/go-oidc/v3 v3.18.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/mattn/go-colorable v0.1.13 + github.com/redis/go-redis/v9 v9.18.0 github.com/sirupsen/logrus v1.9.3 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.47.0 + golang.org/x/oauth2 v0.36.0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.20.2 k8s.io/api v0.35.1 @@ -45,6 +50,7 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect @@ -54,6 +60,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -104,10 +111,10 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/term v0.39.0 // indirect golang.org/x/time v0.12.0 // indirect @@ -133,7 +140,7 @@ require ( ) require ( - gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260423180238-53a8a976b540 + gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260430152421-88c087f0cea0 github.com/golang/protobuf v1.5.4 golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.40.0 // indirect diff --git a/go.sum b/go.sum index 02da231..f9ec13b 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260423180238-53a8a976b540 h1:DX/lMTetxHz4ezEyBI4bKaJwxhO0uXUyGv9pQTV9NYI= -gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260423180238-53a8a976b540/go.mod h1:zgX1KfGcHue8TjuXWN0onGpg3pQPek/lKMfdT6S7TQM= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260430152421-88c087f0cea0 h1:2UggBAWgOJ1MYgkk+RTaWhfTGtzAZ0B9MriZMoHqnq4= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260430152421-88c087f0cea0/go.mod h1:AgOh1lkPHyRgBf3/s1btKcAqke/33LbKYarTD13qeAg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -40,6 +43,10 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -59,6 +66,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= +github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -120,6 +129,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -143,6 +154,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -176,6 +189,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -205,6 +220,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -294,8 +311,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fO github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= @@ -335,6 +352,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= @@ -382,6 +401,8 @@ go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4Etq go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -418,8 +439,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/helmfile.yaml.gotmpl b/helmfile.yaml.gotmpl index fa2ae55..fe66f6d 100644 --- a/helmfile.yaml.gotmpl +++ b/helmfile.yaml.gotmpl @@ -1,5 +1,6 @@ environments: default: + kubeContext: kind-kind values: - databases: postgres: @@ -19,7 +20,7 @@ repositories: releases: - name: postgres-instance - namespace: databases + namespace: softplayer chart: cloudpirates/postgres version: 0.19.1 installed: true @@ -35,7 +36,7 @@ releases: enabled: true whenDeleted: Delete - name: dragonfly - namespace: databases + namespace: softplayer chart: dragonfly/dragonfly version: v1.38.0 installed: true @@ -55,20 +56,18 @@ releases: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1000 - redis: - # -- Enable redis cluster with docker container. - enable: true - image: - repository: bitnamilegacy/redis - # -- Cluster domain. - clusterDomain: 'cluster.local' - auth: - # -- Enable password authentication. - enabled: true - # -- Redis password. - password: dragonfly - master: - service: - ports: - # -- Redis master service port. - redis: 6379 + service: + port: 30379 + type: NodePort + strategicMergePatches: + - apiVersion: v1 + kind: Service + metadata: + name: dragonfly + namespace: softplayer + spec: + ports: + - name: dragonfly + port: 30379 + protocol: TCP + nodePort: 30379 diff --git a/internal/controllers/accounts.go b/internal/controllers/accounts.go index 53c34db..df3e21d 100644 --- a/internal/controllers/accounts.go +++ b/internal/controllers/accounts.go @@ -3,21 +3,34 @@ package controllers import ( "context" "database/sql" + "errors" + "fmt" + "time" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/hash" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" + "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/redis/go-redis/v9" ) type AccountController struct { - Params AccountParams - DB *sql.DB - DevMode bool + DB *sql.DB + Redis *redis.Client + DevMode bool + HashCost int16 + AccessTokenTTL time.Duration + RefreshTokenTTL time.Duration + JWTSecret []byte } -type AccountParams struct { - HashCost int16 +type JWT struct { + RefreshToken string + AccessToken string } +type AccountParams struct{} + type AccountData struct { Username string Password string @@ -25,16 +38,67 @@ type AccountData struct { UUID string } -func (c *AccountController) Create(ctx context.Context, data *AccountData) error { +func (c *AccountController) Create(ctx context.Context, data *AccountData) (string, error) { data.UUID = uuid.New().String() - passwordHash, err := hash.HashPassword(data.Password, int(c.Params.HashCost)) + passwordHash, err := hash.HashPassword(data.Password, int(c.HashCost)) if err != nil { - return nil + return "", nil } + query := "INSERT INTO users (uuid, username, email, password_hash) VALUES ($1, $2, $3, $4)" if _, err := c.DB.Query(query, data.UUID, data.Username, data.Email, passwordHash); err != nil { - return err + return "", err } - return nil + + return data.UUID, nil +} + +func (c *AccountController) GenerateAccessToken(userID string) (string, error) { + claims := jwt.MapClaims{ + "user_id": userID, + "type": "access", + "exp": time.Now().Add(c.AccessTokenTTL).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(c.JWTSecret) +} + +func redisKey(id string) string { + return fmt.Sprintf("refresh:%s", id) +} + +func (c *AccountController) GenerateRefreshToken(ctx context.Context, userID string) (string, error) { + tokenID := uuid.New().String() + claims := jwt.MapClaims{ + "user_id": userID, + "token_id": tokenID, + "type": "refresh", + "exp": time.Now().Add(c.RefreshTokenTTL).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + if err := c.Redis.Set(ctx, redisKey(tokenID), userID, c.RefreshTokenTTL).Err(); err != nil { + return "", err + } + return token.SignedString(c.JWTSecret) +} + +// It must validate the refresh token +// Get it's id from the content +// Find a corresponding token in redis, and if it's found, remove it and create a new one +func (c *AccountController) ValidateRefreshToken(ctx context.Context, tokenID, userID string) (string, error) { + log := logger.FromContext(ctx) + userIDRedis := c.Redis.Get(ctx, redisKey(tokenID)).Val() + if err := c.Redis.Del(ctx, redisKey(tokenID)).Err(); err != nil { + log.Error(err, "Couldn't delete redis entry") + return "", err + } + log.Info(userIDRedis) + log.Info(userID) + if userID != userIDRedis { + return "", errors.New("user id doesn't match") + } + return userIDRedis, nil } diff --git a/internal/interceptors/authjwt.go b/internal/interceptors/authjwt.go new file mode 100644 index 0000000..30eca99 --- /dev/null +++ b/internal/interceptors/authjwt.go @@ -0,0 +1,64 @@ +package interceptors + +import ( + "context" + "fmt" + "strings" + + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" + "github.com/golang-jwt/jwt/v5" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type JWTVerifier struct { + secret []byte + serverCtx context.Context +} + +func NewJWTVerifier(ctx context.Context, secret []byte) *JWTVerifier { + return &JWTVerifier{ + serverCtx: ctx, + secret: secret, + } +} + +// This is an interceptors that should verify that a user is authorized +func (v *JWTVerifier) JWTAuthInterceptor( + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, +) (interface{}, error) { + log := logger.FromContext(v.serverCtx).WithValues("method", info.FullMethod) + if !strings.Contains(info.FullMethod, "NoAuth") { + log.Info("Checking the JWT token") + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Error(codes.Unauthenticated, "User is not authorized") + } + + tokenString := md.Get("token")[0] + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") + return v.secret, nil + }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "User is not authorized") + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + fmt.Println(claims["userID"]) + } else { + fmt.Println(err) + } + // Get the token from the metadata + // Validate the token + // Get the user id from the token + } else { + log.Info("Auth is not required for this request") + } + return handler(ctx, req) +} diff --git a/kind-config.yaml b/kind-config.yaml index c69d6d8..58a1f51 100644 --- a/kind-config.yaml +++ b/kind-config.yaml @@ -5,5 +5,5 @@ nodes: extraPortMappings: - containerPort: 30432 hostPort: 30432 - - containerPort: 30431 - hostPort: 30431 + - containerPort: 30379 + hostPort: 30379 diff --git a/main.go b/main.go index 1a24aec..cee7ddd 100644 --- a/main.go +++ b/main.go @@ -6,42 +6,62 @@ import ( "errors" "fmt" "net" - "os" + "strings" + "time" v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/email" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" - "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" - applications_proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/applications/v1" - email_proto "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/email/v1" - "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/environments/v1" + accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/v1" + test "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/test/v1" "github.com/alecthomas/kong" + "github.com/golang-jwt/jwt/v5" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" _ "github.com/lib/pq" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" - ctrl "sigs.k8s.io/controller-runtime" + "google.golang.org/grpc/status" + + "github.com/redis/go-redis/v9" ) -type Serve struct { - Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"` - Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"` - HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"` - Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"` - DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"` - SmtpHost string `env:"SOFTPLAYER_SMTP_HOST"` - SmtpPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"` - SmtpFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"` - SmtpPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"` - DownloadDir string `env:"SOFTPLAYER_DOWNLOAD_DIR" default:"/tmp/softplayer"` - DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` +var CLI struct { + Serve Serve `cmd:"" help:"Start the grpc server"` + Migrate Migrate `cmd:"" help:"Run the database migrations"` } -var CLI struct { - Serve Serve `cmd:"" help:"Start the grpc server"` +type Serve struct { + // Service related + Port int64 `short:"p" env:"SOFTPLAYER_PORT" default:"4020"` + Host string `env:"SOFTPLAYER_HOST" default:"0.0.0.0"` + HashCost int16 `env:"SOFTPLAYER_HASH_COST" default:"1"` + // SMTP Config + SMTPHost string `env:"SOFTPLAYER_SMTP_HOST"` + SMTPPort string `env:"SOFTPLAYER_SMTP_PORT" default:"587"` + SMTPFrom string `env:"SOFTPLAYER_SMTP_FROM" default:"overlord@badhouseplants.net"` + SMTPPassword string `env:"SOFTPLAYER_SMTP_PASSWORD"` + // Database and redis + DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` + RedisHost string `env:"SOFTPLAYER_REDIS_HOST"` + // JWT parameters + RefrestTokenTTL time.Duration `default:"8h"` + AccessTokenTTL time.Duration `default:"15m"` + JWTSecret string `default:"qwertyu9"` + // Dev and logging + Reflection bool `env:"SOFTPLAYER_REFLECTION" default:"false"` + DevMode bool `env:"SOFTPLAYER_DEV_MODE" default:"false"` +} + +type Migrate struct { + DBConnectionString string `env:"SOFTPLAYER_DB_CONNECTION_STRING"` + MigrationsPath string `env:"SOFTPLAYER_DB_MIGRATIOON_PATH" default:"file://migrations"` } func main() { @@ -52,15 +72,25 @@ func main() { if err := server(ctx, CLI.Serve); err != nil { panic(err) } + case "migrate": + if err := migrateDB(ctx, CLI.Migrate); err != nil { + panic(err) + } default: panic(kongCtx.Command()) } } -func migrateDB(ctx context.Context, db *sql.DB) error { +// Migrate the database to the latest version +func migrateDB(ctx context.Context, params Migrate) error { log := logger.FromContext(ctx) - log.Info("Starting a database migration driver") + db, err := sql.Open("postgres", params.DBConnectionString) + if err != nil { + log.Error(err, "Couldn't start a database driver") + return err + } + driver, err := postgres.WithInstance(db, &postgres.Config{}) if err != nil { log.Error(err, "Couldn't start a database migration driver") @@ -69,7 +99,7 @@ func migrateDB(ctx context.Context, db *sql.DB) error { log.Info("Preparing database migrations") m, err := migrate.NewWithDatabaseInstance( - "file://migrations", + params.MigrationsPath, "postgres", driver) if err != nil { log.Error(err, "Couldn't perform database migrations") @@ -77,7 +107,7 @@ func migrateDB(ctx context.Context, db *sql.DB) error { } log.Info("Starting database migrations") - err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to r + err = m.Up() if err != nil { if errors.Is(err, migrate.ErrNoChange) { log.Info("Database is already up-to-date") @@ -90,67 +120,102 @@ func migrateDB(ctx context.Context, db *sql.DB) error { return nil } +// Run the grpc backend server func server(ctx context.Context, params Serve) error { // Make sure the download dir exists log := logger.FromContext(ctx) - if err := os.MkdirAll(params.DownloadDir, 0o777); err != nil { - log.Error(err, "Coulnd't create a download dir") - return err - } - // Init a logger - log.Info("Starting a kubernetes manager") - controller, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) - if err != nil { - log.Error(err, "Coulnd't start a kube manager") - return err - } + //log.Info("Starting a kubernetes manager") + //controller, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) + //if err != nil { + // log.Error(err, "Coulnd't start a kube manager") + // return err + //} // TODO: Handle the error - go func() { - if err := controller.Start(ctx); err != nil { - panic(err) - } - }() + //go func() { + // if err := controller.Start(ctx); err != nil { + // panic(err) + // } + //}() log.Info("Opening a database connection") - db, err := sql.Open("postgres", "postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable") + + db, err := sql.Open("postgres", params.DBConnectionString) if err != nil { log.Error(err, "Couldn't start a database driver") return err } - if err := migrateDB(ctx, db); err != nil { - log.Error(err, "Error occured while executing migrations") - return err - } - emailConfig := email.EmailConf{ - From: params.SmtpFrom, - Password: params.SmtpPassword, - SmtpHost: params.SmtpHost, - SmtpPort: params.SmtpPort, - } + //emailConfig := email.EmailConf{ + // From: params.SmtpFrom, + // Password: params.SmtpPassword, + // SmtpHost: params.SmtpHost, + // SmtpPort: params.SmtpPort, + //} address := fmt.Sprintf("%s:%d", params.Host, params.Port) lis, err := net.Listen("tcp", address) if err != nil { return err } + + // jwtVerifier := interceptors.NewJWTVerifier(ctx, []byte(params.JWTSecret)) + + authFn := func(ctx context.Context) (context.Context, error) { + tokenString, err := auth.AuthFromMD(ctx, "bearer") + if err != nil { + return nil, err + } + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + return []byte(params.JWTSecret), nil + }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) + if err != nil { + return nil, status.Error(codes.Unauthenticated, "User is not authorized") + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + ctx = context.WithValue(ctx, "token_id", claims["token_id"].(string)) + ctx = context.WithValue(ctx, "user_id", claims["user_id"].(string)) + } else { + return ctx, errors.New("claims are missing int the token") + } + return ctx, nil + } + authReqServices := func(ctx context.Context, callMeta interceptors.CallMeta) bool { + return !strings.Contains(callMeta.Service, "NoAuth") + } grpcServer := grpc.NewServer( - grpc.UnaryInterceptor(grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info"))), + grpc.ChainUnaryInterceptor( + grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")), + // jwtVerifier.JWTAuthInterceptor, + selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(authReqServices)), + ), grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))), ) + rdb := redis.NewClient(&redis.Options{ + Addr: params.RedisHost, + }) if params.Reflection { reflection.Register(grpcServer) } - environments.RegisterEnvironmentsServer(grpcServer, v1.NewapiGrpcImpl(controller, log)) - accounts.RegisterAccountsServer(grpcServer, v1.NewAccountRPCImpl(db, params.HashCost, &emailConfig, params.DevMode)) - email_proto.RegisterEmailValidationServer(grpcServer, v1.InitEmailServer(controller, &emailConfig, params.DevMode)) - applications_proto.RegisterApplicationsServer(grpcServer, v1.NewApplicationsGrpcImpl(controller, log)) - + accountCtrl := &controllers.AccountController{ + HashCost: params.HashCost, + DB: db, + DevMode: params.DevMode, + RefreshTokenTTL: params.RefrestTokenTTL, + AccessTokenTTL: params.AccessTokenTTL, + JWTSecret: []byte(params.JWTSecret), + Redis: rdb, + } + accounts.RegisterAccountsNoAuthServiceServer(grpcServer, v1.NewAccountNoAuthRPCImpl(accountCtrl)) + accounts.RegisterAccountsAuthServiceServer(grpcServer, v1.NewAccountAuthRPCImpl(accountCtrl)) + test.RegisterTestAuthServiceServer(grpcServer, v1.NewTestAuthRPCImpl()) + test.RegisterTestNoAuthServiceServer(grpcServer, v1.NewTestNoAuthRPCImpl()) if err := grpcServer.Serve(lis); err != nil { return err }