Files
ez-api/internal/service/token.go

77 lines
2.0 KiB
Go

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
}