From f15608e0ab0b34f9fe019cf4edf7404605d8fca0 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Tue, 5 May 2026 22:56:56 +0200 Subject: [PATCH] Basic internal auth implementation Signed-off-by: Nikolai Rodionov --- .gitignore | 1 + Taskfile.yml | 64 ++++++++++ api/v1/accounts_auth.go | 42 ------- api/v1/accounts_no_auth.go | 114 ++++++++++++------ go.mod | 4 +- go.sum | 12 -- helm/softplayer-backend/templates/NOTES.txt | 35 ------ .../templates/deployment.yaml | 28 +++++ .../templates/extra/database.yaml | 14 +++ .../templates/extra/dragonfly.yaml | 17 +++ .../templates/{ => traefik}/ingressroute.yaml | 6 +- .../templates/{ => traefik}/middleware.yaml | 0 internal/controllers/accounts.go | 30 ++++- internal/controllers/applications.go | 3 +- main.go | 10 +- migrations/20260425183515_init.down.sql | 1 - .../20260506200734_accounts_init.down.sql | 1 + ...ql => 20260506200734_accounts_init.up.sql} | 6 +- 18 files changed, 242 insertions(+), 146 deletions(-) create mode 100644 helm/softplayer-backend/templates/extra/database.yaml create mode 100644 helm/softplayer-backend/templates/extra/dragonfly.yaml rename helm/softplayer-backend/templates/{ => traefik}/ingressroute.yaml (77%) rename helm/softplayer-backend/templates/{ => traefik}/middleware.yaml (100%) delete mode 100644 migrations/20260425183515_init.down.sql create mode 100644 migrations/20260506200734_accounts_init.down.sql rename migrations/{20260425183515_init.up.sql => 20260506200734_accounts_init.up.sql} (66%) diff --git a/.gitignore b/.gitignore index 567609b..d5697ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build/ +bin/ diff --git a/Taskfile.yml b/Taskfile.yml index a1adc41..b0e3b0b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,6 +2,11 @@ version: "3" +vars: + LOCALBIN: + sh: echo "$(pwd)/bin" + MIGRATE: "{{.LOCALBIN}}/migrate" + tasks: build: desc: Build go code @@ -38,3 +43,62 @@ tasks: helmfile-deploy: desc: Deploy the helmfile for the local dev cmd: helmfile apply + + get-proto-from-branch: + desc: Get the latest version of generated protobuf code from the branch + silent: true + vars: + WORKDIR: + sh: mktemp -d + cmds: + - git clone git@gitea.badhouseplants.net:softplayer/softplayer-go-proto.git '{{ .WORKDIR }}' + - git -C '{{ .WORKDIR }}' checkout '{{ .CLI_ARGS }}' + - go get gitea.badhouseplants.net/softplayer/softplayer-go-proto@$(git -C '{{ .WORKDIR }}' rev-parse HEAD) + - rm -rf '{{ .WORKDIR }}' + - go mod tidy + + add-migration: + desc: Add a new database migration + silent: true + cmd: "{{.MIGRATE}} create -dir migrations -ext sql {{.CLI_ARGS}}" + + # Install required tools + localbin: + desc: Create local bin directory + silent: true + cmds: + - mkdir -p "{{.LOCALBIN}}" + + migrate: + desc: Download migrate if necessary + silent: true + deps: + - localbin + cmds: + - task: go-install-tool + vars: + TARGET: "{{.MIGRATE}}" + PACKAGE: github.com/golang-migrate/migrate/v4/cmd/migrate + VERSION: latest + + go-install-tool: + internal: true + silent: true + cmd: |- + set -e + TARGET="{{.TARGET}}" + PACKAGE="{{.PACKAGE}}@{{.VERSION}}" + VERSIONED="${TARGET}-{{.VERSION}}" + + if [ -f "$VERSIONED" ] && [ "$(readlink -- "$TARGET" 2>/dev/null)" = "$VERSIONED" ]; then + echo "$PACKAGE already installed" + exit 0 + fi + + echo "Downloading $PACKAGE" + rm -f "$TARGET" + + GOBIN="{{.LOCALBIN}}" go install "$PACKAGE" + + mv "{{.LOCALBIN}}/$(basename "$TARGET")" "$VERSIONED" + ln -sf "$(realpath "$VERSIONED")" "$TARGET" diff --git a/api/v1/accounts_auth.go b/api/v1/accounts_auth.go index b3687c9..2c42abe 100644 --- a/api/v1/accounts_auth.go +++ b/api/v1/accounts_auth.go @@ -1,17 +1,8 @@ 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 { @@ -24,36 +15,3 @@ 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 index d6aad9f..92f80be 100644 --- a/api/v1/accounts_no_auth.go +++ b/api/v1/accounts_no_auth.go @@ -4,11 +4,12 @@ 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/coreos/go-oidc/v3/oidc" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/oauth2" + "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" ) @@ -25,49 +26,88 @@ type AccountsNoAuthServer struct { } func (a *AccountsNoAuthServer) SignIn(ctx context.Context, in *accounts.SignInRequest) (*empty.Empty, error) { - provider, err := oidc.NewProvider(ctx, "https://authentik.badhouseplants.net") + id, err := a.ctrl.Login(ctx, in.GetEmail(), in.GetPassword()) if err != nil { - return nil, err + return nil, status.Error(codes.Aborted, "Couldn't create a user") } - - // 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) + accessToken, err := a.ctrl.GenerateAccessToken(id) if err != nil { - return nil, err + return nil, status.Error(codes.Aborted, "Couldn't generate an access token") } - // 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) + refreshToken, err := a.ctrl.GenerateRefreshToken(ctx, id) if err != nil { - return nil, status.Error(codes.Unauthenticated, "Couldn't verify oauth token") + return nil, status.Error(codes.Aborted, "Couldn't generate an access 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 + header := metadata.New(map[string]string{ + "X-Access-Token": accessToken, + "X-Refresh-Token": refreshToken, + }) + if err := grpc.SetHeader(ctx, header); err != nil { + return nil, status.Error(codes.Aborted, "Couldn't set metadata") } return &emptypb.Empty{}, nil } + +// Create a new account in Softplayer +func (a *AccountsNoAuthServer) SignUp(ctx context.Context, in *accounts.SignUpRequest) (*empty.Empty, error) { + data := &controllers.AccountData{ + Password: in.GetPassword(), + Email: in.GetEmail(), + } + id, err := a.ctrl.Create(ctx, data) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't create a user") + } + + accessToken, err := a.ctrl.GenerateAccessToken(id) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't generate an access token") + } + + refreshToken, err := a.ctrl.GenerateRefreshToken(ctx, id) + if err != nil { + return nil, status.Error(codes.Aborted, "Couldn't generate an access token") + } + + header := metadata.New(map[string]string{ + "X-Access-Token": accessToken, + "X-Refresh-Token": refreshToken, + }) + if err := grpc.SetHeader(ctx, header); err != nil { + return nil, status.Error(codes.Aborted, "Couldn't set metadata") + } + return &emptypb.Empty{}, nil +} + +func (a *AccountsAuthServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { + 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/go.mod b/go.mod index 4cb682a..73b7da3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ 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 @@ -20,7 +19,6 @@ require ( 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 @@ -60,7 +58,6 @@ 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 @@ -115,6 +112,7 @@ require ( 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.36.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 diff --git a/go.sum b/go.sum index 1257e52..2f9bebd 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,6 @@ 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= @@ -129,8 +127,6 @@ 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= @@ -189,8 +185,6 @@ 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/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= @@ -243,15 +237,10 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= @@ -464,7 +453,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= diff --git a/helm/softplayer-backend/templates/NOTES.txt b/helm/softplayer-backend/templates/NOTES.txt index 59e3319..e69de29 100644 --- a/helm/softplayer-backend/templates/NOTES.txt +++ b/helm/softplayer-backend/templates/NOTES.txt @@ -1,35 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.httpRoute.enabled }} -{{- if .Values.httpRoute.hostnames }} - export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} -{{- else }} - export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") - {{- end }} -{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} - echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" - - NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. - The rules can be set for path, method, header and query parameters. - You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' -{{- end }} -{{- else if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "softplayer-backend.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "softplayer-backend.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "softplayer-backend.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "softplayer-backend.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/helm/softplayer-backend/templates/deployment.yaml b/helm/softplayer-backend/templates/deployment.yaml index 712595b..197b08e 100644 --- a/helm/softplayer-backend/templates/deployment.yaml +++ b/helm/softplayer-backend/templates/deployment.yaml @@ -32,6 +32,26 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} + initContainers: + - name: {{ .Chart.Name }}-migrations + env: + - name: SOFTPLAYER_DB_CONNECTION_STRING + valueFrom: + secretKeyRef: + name: {{ include "softplayer-backend.fullname" . }}-db-creds + key: CONNECTION_STRING + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - migrate + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} containers: - name: {{ .Chart.Name }} {{- with .Values.securityContext }} @@ -40,6 +60,14 @@ spec: {{- end }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: SOFTPLAYER_DB_CONNECTION_STRING + valueFrom: + secretKeyRef: + name: {{ include "softplayer-backend.fullname" . }}-db-creds + key: CONNECTION_STRING + - name: SOFTPLAYER_REDIS_HOST + value: {{ include "softplayer-backend.fullname" . }}-dragonfly:6379 args: - serve - --dev-mode diff --git a/helm/softplayer-backend/templates/extra/database.yaml b/helm/softplayer-backend/templates/extra/database.yaml new file mode 100644 index 0000000..14d2d6e --- /dev/null +++ b/helm/softplayer-backend/templates/extra/database.yaml @@ -0,0 +1,14 @@ +apiVersion: kinda.rocks/v1beta1 +kind: Database +metadata: + name: {{ include "softplayer-backend.fullname" . }} + labels: + {{- include "softplayer-backend.labels" . | nindent 4 }} +spec: + backup: + cron: 0 0 * * * + enable: false + deletionProtected: true + instance: cloudnative-pg17-softplayer + postgres: {} + secretName: {{ include "softplayer-backend.fullname" . }}-db-creds diff --git a/helm/softplayer-backend/templates/extra/dragonfly.yaml b/helm/softplayer-backend/templates/extra/dragonfly.yaml new file mode 100644 index 0000000..c0d0b3f --- /dev/null +++ b/helm/softplayer-backend/templates/extra/dragonfly.yaml @@ -0,0 +1,17 @@ +apiVersion: dragonflydb.io/v1alpha1 +kind: Dragonfly +metadata: + name: {{ include "softplayer-backend.fullname" . }}-dragonfly + labels: + {{- include "softplayer-backend.labels" . | nindent 4 }} +spec: + imagePullPolicy: Always + networkPolicyEnabled: true + replicas: 1 + resources: + limits: + cpu: 20m + memory: 256Mi + requests: + cpu: 20m + memory: 128Mi diff --git a/helm/softplayer-backend/templates/ingressroute.yaml b/helm/softplayer-backend/templates/traefik/ingressroute.yaml similarity index 77% rename from helm/softplayer-backend/templates/ingressroute.yaml rename to helm/softplayer-backend/templates/traefik/ingressroute.yaml index b9c1d8f..c007c75 100644 --- a/helm/softplayer-backend/templates/ingressroute.yaml +++ b/helm/softplayer-backend/templates/traefik/ingressroute.yaml @@ -1,7 +1,9 @@ apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: - name: grpc-route + name: {{ include "softplayer-backend.fullname" . }} + labels: + {{- include "softplayer-backend.labels" . | nindent 4 }} annotations: external-dns.alpha.kubernetes.io/target: {{ .Values.ingressRoute.target }} kubernetes.io/ingress.class: {{ .Values.ingressRoute.class }} @@ -15,7 +17,7 @@ spec: - name: {{ include "softplayer-backend.fullname" . | replace "-" "" }} services: - name: {{ include "softplayer-backend.fullname" . }} - port: 4020 + port: {{ .Values.service.port }} scheme: h2c tls: secretName: {{ .Values.ingressRoute.url }} diff --git a/helm/softplayer-backend/templates/middleware.yaml b/helm/softplayer-backend/templates/traefik/middleware.yaml similarity index 100% rename from helm/softplayer-backend/templates/middleware.yaml rename to helm/softplayer-backend/templates/traefik/middleware.yaml diff --git a/internal/controllers/accounts.go b/internal/controllers/accounts.go index df3e21d..d941f4a 100644 --- a/internal/controllers/accounts.go +++ b/internal/controllers/accounts.go @@ -32,28 +32,50 @@ type JWT struct { type AccountParams struct{} type AccountData struct { - Username string Password string Email string UUID string } func (c *AccountController) Create(ctx context.Context, data *AccountData) (string, error) { + log := logger.FromContext(ctx) data.UUID = uuid.New().String() passwordHash, err := hash.HashPassword(data.Password, int(c.HashCost)) if err != nil { + log.Error(err, "Couldn't crate the password hash") 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 { + query := "INSERT INTO accounts (uuid, email, password_hash) VALUES ($1, $2, $3)" + if _, err := c.DB.Query(query, data.UUID, data.Email, passwordHash); err != nil { + log.Error(err, "Couldn't create a user in the database") return "", err } return data.UUID, nil } +func (c *AccountController) Login(ctx context.Context, email, password string) (string, error) { + log := logger.FromContext(ctx) + query := "SELECT uuid, password_hash FROM accounts WHERE email = $1;" + + var passwordHash string + var uuid string + + if err := c.DB.QueryRow(query, email).Scan(&uuid, &passwordHash); err != nil { + log.Error(err, "Couldn't get a user from the database") + return "", err + } + + if err := hash.CheckPasswordHash(password, passwordHash); err != nil { + log.Error(err, "Wrong password") + return "", err + } + + return uuid, nil +} + func (c *AccountController) GenerateAccessToken(userID string) (string, error) { claims := jwt.MapClaims{ "user_id": userID, @@ -95,8 +117,6 @@ func (c *AccountController) ValidateRefreshToken(ctx context.Context, tokenID, u 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") } diff --git a/internal/controllers/applications.go b/internal/controllers/applications.go index 483c61b..623f971 100644 --- a/internal/controllers/applications.go +++ b/internal/controllers/applications.go @@ -68,7 +68,6 @@ func (app *Application) Create(ctx context.Context) error { } controller, err := ctrl.NewManager(conf, ctrl.Options{}) - if err != nil { return err } @@ -90,7 +89,7 @@ func (app *Application) Create(ctx context.Context) error { )[0:20] goPath := os.TempDir() + "/softplayer/" + formattedName - if err := os.MkdirAll(goPath, 0777); err != nil { + if err := os.MkdirAll(goPath, 0o777); err != nil { return err } diff --git a/main.go b/main.go index cee7ddd..d3e8b63 100644 --- a/main.go +++ b/main.go @@ -177,10 +177,14 @@ func server(ctx context.Context, params Serve) error { } 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)) + userIDRaw, userIDOk := claims["user_id"] + if !userIDOk { + return nil, errors.New("required claims are missing in the token") + } + userID := userIDRaw.(string) + log.Info(userID) } else { - return ctx, errors.New("claims are missing int the token") + return ctx, errors.New("claims are missing in the token") } return ctx, nil } diff --git a/migrations/20260425183515_init.down.sql b/migrations/20260425183515_init.down.sql deleted file mode 100644 index c99ddcd..0000000 --- a/migrations/20260425183515_init.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS users; diff --git a/migrations/20260506200734_accounts_init.down.sql b/migrations/20260506200734_accounts_init.down.sql new file mode 100644 index 0000000..1616db4 --- /dev/null +++ b/migrations/20260506200734_accounts_init.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS accounts; diff --git a/migrations/20260425183515_init.up.sql b/migrations/20260506200734_accounts_init.up.sql similarity index 66% rename from migrations/20260425183515_init.up.sql rename to migrations/20260506200734_accounts_init.up.sql index 83b5410..9c2a401 100644 --- a/migrations/20260425183515_init.up.sql +++ b/migrations/20260506200734_accounts_init.up.sql @@ -1,9 +1,7 @@ -CREATE TABLE IF NOT EXISTS users ( +CREATE TABLE IF NOT EXISTS accounts ( uuid UUID PRIMARY KEY, - username VARCHAR(10) NOT NULL - CHECK (username ~ '^[a-z0-9]{1,10}$') UNIQUE, email VARCHAR(255) NOT NULL - CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), + CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') UNIQUE, password_hash TEXT NOT NULL, email_verified BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -- 2.49.1