package service import ( "context" "errors" "fmt" "strconv" "github.com/ez-api/foundation/tokenhash" "github.com/redis/go-redis/v9" ) type TokenService struct { rdb *redis.Client } func NewTokenService(rdb *redis.Client) *TokenService { return &TokenService{rdb: rdb} } type TokenInfo struct { MasterID uint IssuedAtEpoch int64 Status string Group string } // ValidateToken checks a child key against Redis for validity. // This is designed to be called by the data plane (balancer). func (s *TokenService) ValidateToken(ctx context.Context, token string) (*TokenInfo, error) { tokenHash := tokenhash.HashToken(token) tokenKey := fmt.Sprintf("auth:token:%s", tokenHash) // 1. Get token metadata from Redis tokenData, err := s.rdb.HGetAll(ctx, tokenKey).Result() if err != nil { return nil, fmt.Errorf("failed to get token data: %w", err) } if len(tokenData) == 0 { return nil, errors.New("token not found") } if tokenData["status"] != "active" { return nil, errors.New("token is not active") } masterID, _ := strconv.ParseUint(tokenData["master_id"], 10, 64) issuedAtEpoch, _ := strconv.ParseInt(tokenData["issued_at_epoch"], 10, 64) // 2. Get master metadata from Redis masterKey := fmt.Sprintf("auth:master:%d", masterID) masterData, err := s.rdb.HGetAll(ctx, masterKey).Result() if err != nil { return nil, fmt.Errorf("failed to get master metadata: %w", err) } if len(masterData) == 0 { return nil, errors.New("master metadata not found") } masterStatus := masterData["status"] if masterStatus != "" && masterStatus != "active" { return nil, errors.New("master is not active") } masterEpoch, _ := strconv.ParseInt(masterData["epoch"], 10, 64) // 3. Core Epoch Validation if issuedAtEpoch < masterEpoch { return nil, errors.New("token revoked due to master key rotation") } return &TokenInfo{ MasterID: uint(masterID), IssuedAtEpoch: issuedAtEpoch, Status: tokenData["status"], Group: tokenData["group"], }, nil }