diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 08bf029..3a44805 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -1 +1,25 @@ package cache + +import ( + "context" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +const ( + CacheFolderToken = "token" +) + +func buildKey(folder, key string) string { + return fmt.Sprintf("%s:%s", folder, key) +} + +func GetFromCache(ctx context.Context, redis *redis.Client, folder, key string) string { + return redis.Get(ctx, buildKey(folder, key)).Val() +} + +func SaveToCache(ctx context.Context, redis *redis.Client, folder, key, value string, ttl time.Duration) error { + return redis.Set(ctx, buildKey(folder, key), value, ttl).Err() +} diff --git a/internal/controllers/tokens.go b/internal/controllers/tokens.go index dbc0b79..f5f15d1 100644 --- a/internal/controllers/tokens.go +++ b/internal/controllers/tokens.go @@ -7,11 +7,11 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "regexp" "testing" "time" + "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/cache" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/token" "gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository" @@ -73,9 +73,7 @@ func (ctrl *TokenController) VerifyTokenOwner(ctx context.Context, userID, token log := logger.FromContext(ctx).WithValues("uuid", tokenID, "user_id", userID) log.V(2).Info("Verifying the token owner") - // First try to get from the redis - redisKey := fmt.Sprintf("token:%s", tokenID) - realUserID := ctrl.Redis.Get(ctx, redisKey).Val() + realUserID := cache.GetFromCache(ctx, ctrl.Redis, cache.CacheFolderToken, tokenID) // If not found in cache, get from postgres if realUserID == "" { query := "SELECT user_id FROM tokens WHERE uuid = $1;" @@ -90,8 +88,7 @@ func (ctrl *TokenController) VerifyTokenOwner(ctx context.Context, userID, token if realUserID != userID { return ErrUserTokenMismatch } - err := ctrl.Redis.Set(ctx, redisKey, realUserID, time.Hour) - if err != nil { + if err := cache.SaveToCache(ctx, ctrl.Redis, cache.CacheFolderToken, realUserID, tokenID, time.Hour); err != nil { log.Info("Couldn't write to cache", "error", err) } return nil diff --git a/internal/controllers/tokens_test.go b/internal/controllers/tokens_test.go index 69106d8..91429a5 100644 --- a/internal/controllers/tokens_test.go +++ b/internal/controllers/tokens_test.go @@ -17,7 +17,7 @@ func newTestTokensController(ctx context.Context) *controllers.TokenController { } } -func TestCreateToken_Success(t *testing.T) { +func TestIntegrationCreateToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ @@ -43,7 +43,7 @@ func TestCreateToken_Success(t *testing.T) { assert.NotEmpty(t, tokenVal) } -func TestCreateToken_UserNotExist(t *testing.T) { +func TestIntegrationCreateToken_UserNotExist(t *testing.T) { tokenData := &controllers.TokenData{ Name: "Test Token", UserID: uuid.NewString(), @@ -62,7 +62,7 @@ func TestCreateToken_UserNotExist(t *testing.T) { assert.Empty(t, tokenVal) } -func TestGetToken_Success(t *testing.T) { +func TestIntegrationGetToken_Success(t *testing.T) { // Create a user for the token ctrlAccount := newTestAccountController(t.Context()) accountData := &controllers.AccountData{ @@ -95,3 +95,70 @@ func TestGetToken_Success(t *testing.T) { assert.Equal(t, tokenData.Name, token.Name) assert.Equal(t, tokenData.ExpiresAt.Truncate(time.Second), token.ExpiresAt.Truncate(time.Second)) } + +func TestIntegrationGetToken_NotExists(t *testing.T) { + ctrl := newTestTokensController(t.Context()) + token, err := ctrl.Get(t.Context(), uuid.NewString(), uuid.NewString()) + assert.Error(t, err) + assert.ErrorIs(t, err, controllers.ErrTokenNotFound) + assert.Empty(t, token) +} + +func TestIntegrationVerifyTokenOwner_Success(t *testing.T) { + // Create a user for the token + ctrlAccount := newTestAccountController(t.Context()) + accountData := &controllers.AccountData{ + Password: "qwertyu9", + Email: newTestUniqueEmail("accounts"), + } + + userID, err := ctrlAccount.Create(t.Context(), accountData) + assert.NoError(t, err) + + tokenData := &controllers.TokenData{ + Name: "Test Token", + UserID: userID, + ExpiresAt: time.Now().Add(time.Second * 5), + Scopes: map[string][]string{ + "Test": {"test", "test2"}, + }, + } + + ctrl := newTestTokensController(t.Context()) + _, tokenID, err := ctrl.Create(t.Context(), tokenData) + assert.NoError(t, err) + assert.NoError(t, ctrl.VerifyTokenOwner(t.Context(), userID, tokenID)) +} + +func TestIntegrationVerifyTokenOwner_WrongOwner(t *testing.T) { + // Create a user for the token + ctrlAccount := newTestAccountController(t.Context()) + accountData := &controllers.AccountData{ + Password: "qwertyu9", + Email: newTestUniqueEmail("accounts"), + } + + secondAccountData := &controllers.AccountData{ + Password: "qwertyu9", + Email: newTestUniqueEmail("accounts"), + } + userID, err := ctrlAccount.Create(t.Context(), accountData) + assert.NoError(t, err) + + secondUserID, err := ctrlAccount.Create(t.Context(), secondAccountData) + assert.NoError(t, err) + + tokenData := &controllers.TokenData{ + Name: "Test Token", + UserID: userID, + ExpiresAt: time.Now().Add(time.Second * 5), + Scopes: map[string][]string{ + "Test": {"test", "test2"}, + }, + } + + ctrl := newTestTokensController(t.Context()) + _, tokenID, err := ctrl.Create(t.Context(), tokenData) + assert.NoError(t, err) + assert.ErrorIs(t, ctrl.VerifyTokenOwner(t.Context(), secondUserID, tokenID), controllers.ErrUserTokenMismatch) +}