package service import ( "crypto/rand" "encoding/hex" "errors" "fmt" "github.com/ez-api/ez-api/internal/model" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type MasterService struct { db *gorm.DB } func NewMasterService(db *gorm.DB) *MasterService { return &MasterService{db: db} } func (s *MasterService) CreateMaster(name, group string, maxChildKeys, globalQPS int) (*model.Master, string, error) { rawMasterKey, err := generateRandomKey(32) if err != nil { return nil, "", fmt.Errorf("failed to generate master key: %w", err) } hashedMasterKey, err := bcrypt.GenerateFromPassword([]byte(rawMasterKey), bcrypt.DefaultCost) if err != nil { return nil, "", fmt.Errorf("failed to hash master key: %w", err) } master := &model.Master{ Name: name, MasterKey: string(hashedMasterKey), Group: group, MaxChildKeys: maxChildKeys, GlobalQPS: globalQPS, Status: "active", Epoch: 1, } if err := s.db.Create(master).Error; err != nil { return nil, "", err } return master, rawMasterKey, nil } 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 } for _, master := range masters { if bcrypt.CompareHashAndPassword([]byte(master.MasterKey), []byte(masterKey)) == nil { return &master, nil } } return nil, errors.New("invalid master key") } func (s *MasterService) IssueChildKey(masterID uint, group string, scopes string) (*model.Key, string, error) { var master model.Master if err := s.db.First(&master, masterID).Error; err != nil { return nil, "", fmt.Errorf("master not found: %w", err) } var count int64 s.db.Model(&model.Key{}).Where("master_id = ?", masterID).Count(&count) if count >= int64(master.MaxChildKeys) { return nil, "", fmt.Errorf("child key limit reached for master %d", masterID) } rawChildKey, err := generateRandomKey(32) if err != nil { return nil, "", fmt.Errorf("failed to generate child key: %w", err) } key := &model.Key{ MasterID: masterID, KeySecret: rawChildKey, // In a real system, this should also be hashed Group: group, Scopes: scopes, IssuedAtEpoch: master.Epoch, Status: "active", } if err := s.db.Create(key).Error; err != nil { return nil, "", err } return key, rawChildKey, nil } func generateRandomKey(length int) (string, error) { bytes := make([]byte, length) if _, err := rand.Read(bytes); err != nil { return "", err } return hex.EncodeToString(bytes), nil }