mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(auth): enhance security with token hashing and sync integration
- Add token hash fields to Master and Key models for indexed lookups - Implement SyncService integration in admin and master handlers - Update master key validation with backward-compatible digest lookup - Hash child keys in database and store token digests for Redis sync - Add master metadata sync to Redis for balancer validation - Ensure backward compatibility with legacy rows during migration
This commit is contained in:
@@ -5,8 +5,10 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ez-api/ez-api/internal/model"
|
||||
"github.com/ez-api/ez-api/internal/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -30,14 +32,17 @@ func (s *MasterService) CreateMaster(name, group string, maxChildKeys, globalQPS
|
||||
return nil, "", fmt.Errorf("failed to hash master key: %w", err)
|
||||
}
|
||||
|
||||
masterKeyDigest := util.HashToken(rawMasterKey)
|
||||
|
||||
master := &model.Master{
|
||||
Name: name,
|
||||
MasterKey: string(hashedMasterKey),
|
||||
Group: group,
|
||||
MaxChildKeys: maxChildKeys,
|
||||
GlobalQPS: globalQPS,
|
||||
Status: "active",
|
||||
Epoch: 1,
|
||||
Name: name,
|
||||
MasterKey: string(hashedMasterKey),
|
||||
MasterKeyDigest: masterKeyDigest,
|
||||
Group: group,
|
||||
MaxChildKeys: maxChildKeys,
|
||||
GlobalQPS: globalQPS,
|
||||
Status: "active",
|
||||
Epoch: 1,
|
||||
}
|
||||
|
||||
if err := s.db.Create(master).Error; err != nil {
|
||||
@@ -48,20 +53,42 @@ func (s *MasterService) CreateMaster(name, group string, maxChildKeys, globalQPS
|
||||
}
|
||||
|
||||
func (s *MasterService) ValidateMasterKey(masterKey string) (*model.Master, error) {
|
||||
// This is inefficient. We should query by a hash or an indexed field.
|
||||
// For now, we iterate. In a real system, this needs optimization.
|
||||
var masters []model.Master
|
||||
if err := s.db.Find(&masters).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest := util.HashToken(masterKey)
|
||||
|
||||
for _, master := range masters {
|
||||
if bcrypt.CompareHashAndPassword([]byte(master.MasterKey), []byte(masterKey)) == nil {
|
||||
return &master, nil
|
||||
var master model.Master
|
||||
if err := s.db.Where("master_key_digest = ?", digest).First(&master).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Backward compatibility: look for legacy rows without digest.
|
||||
var masters []model.Master
|
||||
if err := s.db.Where("master_key_digest = '' OR master_key_digest IS NULL").Find(&masters).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range masters {
|
||||
if bcrypt.CompareHashAndPassword([]byte(m.MasterKey), []byte(masterKey)) == nil {
|
||||
master = m
|
||||
// Opportunistically backfill digest for next time.
|
||||
if strings.TrimSpace(m.MasterKeyDigest) == "" {
|
||||
_ = s.db.Model(&m).Update("master_key_digest", digest).Error
|
||||
}
|
||||
goto verified
|
||||
}
|
||||
}
|
||||
return nil, errors.New("invalid master key")
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid master key")
|
||||
if bcrypt.CompareHashAndPassword([]byte(master.MasterKey), []byte(masterKey)) != nil {
|
||||
return nil, errors.New("invalid master key")
|
||||
}
|
||||
|
||||
verified:
|
||||
if master.Status != "active" {
|
||||
return nil, fmt.Errorf("master is not active")
|
||||
}
|
||||
|
||||
return &master, nil
|
||||
}
|
||||
|
||||
func (s *MasterService) IssueChildKey(masterID uint, group string, scopes string) (*model.Key, string, error) {
|
||||
@@ -81,9 +108,17 @@ func (s *MasterService) IssueChildKey(masterID uint, group string, scopes string
|
||||
return nil, "", fmt.Errorf("failed to generate child key: %w", err)
|
||||
}
|
||||
|
||||
tokenHash := util.HashToken(rawChildKey)
|
||||
|
||||
hashedChildKey, err := bcrypt.GenerateFromPassword([]byte(rawChildKey), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to hash child key: %w", err)
|
||||
}
|
||||
|
||||
key := &model.Key{
|
||||
MasterID: masterID,
|
||||
KeySecret: rawChildKey, // In a real system, this should also be hashed
|
||||
KeySecret: string(hashedChildKey),
|
||||
TokenHash: tokenHash,
|
||||
Group: group,
|
||||
Scopes: scopes,
|
||||
IssuedAtEpoch: master.Epoch,
|
||||
|
||||
Reference in New Issue
Block a user