From 833834bef0a0e20267945111d39cf15ae3ee2dd2 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Tue, 28 Apr 2026 22:54:51 +0200 Subject: [PATCH] Something about auth Signed-off-by: Nikolai Rodionov --- api/v1/accounts_auth.go | 42 ++++++++++++++++++++++++++++++++ api/v1/accounts_no_auth.go | 38 ----------------------------- go.mod | 3 ++- go.sum | 9 +++++-- internal/controllers/accounts.go | 35 ++++++++++---------------- main.go | 37 +++++++++++++++++++++++++--- 6 files changed, 98 insertions(+), 66 deletions(-) diff --git a/api/v1/accounts_auth.go b/api/v1/accounts_auth.go index 2c42abe..b3687c9 100644 --- a/api/v1/accounts_auth.go +++ b/api/v1/accounts_auth.go @@ -1,8 +1,17 @@ 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 { @@ -15,3 +24,36 @@ 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 e099fc1..4331e9c 100644 --- a/api/v1/accounts_no_auth.go +++ b/api/v1/accounts_no_auth.go @@ -76,41 +76,3 @@ func (a *AccountsNoAuthServer) ResetPassword(ctx context.Context, in *accounts.R func (acc *AccountsNoAuthServer) NewPassword(ctx context.Context, in *accounts.NewPasswordRequest) (*empty.Empty, error) { return nil, status.Error(codes.Unimplemented, "Endpoint is not implemented") } - -func (a *AccountsNoAuthServer) RefreshToken(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { - log := logger.FromContext(ctx) - - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return nil, status.Error(codes.Unauthenticated, "User is not authorized") - } - - tokenString := md.Get("token")[0] - uuid, err := a.ctrl.ValidateRefreshToken(ctx, tokenString) - 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 1a052d0..4f31774 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( 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 @@ -137,7 +138,7 @@ require ( ) require ( - gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260428111006-efa5c57e6a14 + gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260428191411-8c93fd05025a 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 e3e8e4c..af99e68 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-20260428111006-efa5c57e6a14 h1:PwOWag8dum67a1w/QIP7NlSGPL/Z7rZDHAwjRJjyk3U= -gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260428111006-efa5c57e6a14/go.mod h1:AgOh1lkPHyRgBf3/s1btKcAqke/33LbKYarTD13qeAg= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260428191411-8c93fd05025a h1:8Mo14FqMkUcUNyyVfrTGZOe/BHn412olquEDqL+Fg6c= +gitea.badhouseplants.net/softplayer/softplayer-go-proto v0.0.0-20260428191411-8c93fd05025a/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= @@ -182,6 +185,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= diff --git a/internal/controllers/accounts.go b/internal/controllers/accounts.go index 6495932..df3e21d 100644 --- a/internal/controllers/accounts.go +++ b/internal/controllers/accounts.go @@ -8,6 +8,7 @@ import ( "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" @@ -64,6 +65,10 @@ func (c *AccountController) GenerateAccessToken(userID string) (string, error) { 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{ @@ -74,8 +79,7 @@ func (c *AccountController) GenerateRefreshToken(ctx context.Context, userID str } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - redisKey := fmt.Sprintf("refresh_token:%s", tokenID) - if err := c.Redis.Set(ctx, redisKey, userID, c.RefreshTokenTTL).Err(); err != nil { + if err := c.Redis.Set(ctx, redisKey(tokenID), userID, c.RefreshTokenTTL).Err(); err != nil { return "", err } return token.SignedString(c.JWTSecret) @@ -84,28 +88,15 @@ func (c *AccountController) GenerateRefreshToken(ctx context.Context, userID str // 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, tokenString string) (string, error) { - 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 c.JWTSecret, nil - }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) - if err != nil { - return "", err - } - - var tokenID string - var userID string - if claims, ok := token.Claims.(jwt.MapClaims); ok { - tokenID = claims["token_id"].(string) - userID = claims["user_id"].(string) - } else { - return "", errors.New("token id is not set") - } - - userIDRedis := c.Redis.Get(ctx, tokenID).String() - if c.Redis.Del(ctx, tokenID).Err() != nil { +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") } diff --git a/main.go b/main.go index e79dbeb..f252886 100644 --- a/main.go +++ b/main.go @@ -6,21 +6,27 @@ import ( "errors" "fmt" "net" + "strings" "time" v1 "gitea.badhouseplants.net/softplayer/softplayer-backend/api/v1" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers" - "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/interceptors" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/tools/logger" accounts "gitea.badhouseplants.net/softplayer/softplayer-go-proto/pkg/accounts/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" + "google.golang.org/grpc/status" "github.com/redis/go-redis/v9" ) @@ -154,12 +160,37 @@ func server(ctx context.Context, params Serve) error { return err } - jwtVerifier := interceptors.NewJWTVerifier(ctx, []byte(params.JWTSecret)) + // 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.ChainUnaryInterceptor( grpc_zap.UnaryServerInterceptor(logger.SetupLogger("info")), - jwtVerifier.JWTAuthInterceptor, + // jwtVerifier.JWTAuthInterceptor, + selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(authReqServices)), ), grpc.StreamInterceptor(grpc_zap.StreamServerInterceptor(logger.SetupLogger("info"))), )