package repository import ( "context" "time" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" domrepo "haixun-backend/internal/model/auth/domain/repository" goredis "github.com/redis/go-redis/v9" ) const ( jwtPairPrefix = "auth:jwt:pair:" jwtBlacklistPrefix = "auth:jwt:blacklist:" ) type redisTokenRevokeStore struct { client *goredis.Client } func NewRedisTokenRevokeStore(client *goredis.Client) domrepo.TokenRevokeStore { return &redisTokenRevokeStore{client: client} } func (s *redisTokenRevokeStore) SavePair(ctx context.Context, accessJTI, refreshJTI string, accessTTL, refreshTTL time.Duration) error { if err := s.requireRedis(); err != nil { return err } if accessJTI == "" || refreshJTI == "" { return app.For(code.Auth).InputMissingRequired("jwt pair jti is required") } if err := s.client.Set(ctx, jwtPairPrefix+accessJTI, refreshJTI, minTTL(accessTTL)).Err(); err != nil { return err } return s.client.Set(ctx, jwtPairPrefix+refreshJTI, accessJTI, minTTL(refreshTTL)).Err() } func (s *redisTokenRevokeStore) GetPairedJTI(ctx context.Context, jti string) (string, error) { if err := s.requireRedis(); err != nil { return "", err } value, err := s.client.Get(ctx, jwtPairPrefix+jti).Result() if err == goredis.Nil { return "", nil } return value, err } func (s *redisTokenRevokeStore) DeletePair(ctx context.Context, accessJTI, refreshJTI string) error { if err := s.requireRedis(); err != nil { return err } keys := make([]string, 0, 2) if accessJTI != "" { keys = append(keys, jwtPairPrefix+accessJTI) } if refreshJTI != "" { keys = append(keys, jwtPairPrefix+refreshJTI) } if len(keys) == 0 { return nil } return s.client.Del(ctx, keys...).Err() } func (s *redisTokenRevokeStore) Blacklist(ctx context.Context, jti string, ttl time.Duration) error { if err := s.requireRedis(); err != nil { return err } if jti == "" { return app.For(code.Auth).InputMissingRequired("jti is required") } return s.client.Set(ctx, jwtBlacklistPrefix+jti, "1", minTTL(ttl)).Err() } func (s *redisTokenRevokeStore) IsBlacklisted(ctx context.Context, jti string) (bool, error) { if err := s.requireRedis(); err != nil { return false, err } count, err := s.client.Exists(ctx, jwtBlacklistPrefix+jti).Result() return count > 0, err } func (s *redisTokenRevokeStore) requireRedis() error { if s.client == nil { return app.For(code.Auth).DBUnavailable("Redis is not configured") } return nil } func minTTL(ttl time.Duration) time.Duration { if ttl < time.Second { return time.Second } return ttl }