mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(auth): implement master key authentication system with child key issuance
Add admin and master authentication layers with JWT support. Replace direct key creation with hierarchical master/child key system. Update database schema to support master accounts with configurable limits and epoch-based key revocation. Add health check endpoint with system status monitoring. BREAKING CHANGE: Removed direct POST /keys endpoint in favor of master-based key issuance through /v1/tokens. Database migration requires dropping old User table and creating Master table with new relationships.
This commit is contained in:
106
internal/service/master.go
Normal file
106
internal/service/master.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user