package api import ( "errors" "net/http" "strings" "github.com/ez-api/ez-api/internal/model" "github.com/ez-api/ez-api/internal/service" "github.com/gin-gonic/gin" ) type MasterHandler struct { masterService *service.MasterService syncService *service.SyncService } func NewMasterHandler(masterService *service.MasterService, syncService *service.SyncService) *MasterHandler { return &MasterHandler{masterService: masterService, syncService: syncService} } type IssueChildKeyRequest struct { Group string `json:"group"` Scopes string `json:"scopes"` } // IssueChildKey godoc // @Summary Issue a child key // @Description Issue a new access token (child key) for the authenticated master // @Tags master // @Accept json // @Produce json // @Security MasterAuth // @Param request body IssueChildKeyRequest true "Key Request" // @Success 201 {object} gin.H // @Failure 400 {object} gin.H // @Failure 401 {object} gin.H // @Failure 403 {object} gin.H // @Failure 500 {object} gin.H // @Router /v1/tokens [post] func (h *MasterHandler) IssueChildKey(c *gin.Context) { master, exists := c.Get("master") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"}) return } masterModel := master.(*model.Master) var req IssueChildKeyRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // If group is not specified, inherit from master group := req.Group if strings.TrimSpace(group) == "" { group = masterModel.Group } // Security: Ensure the requested group is allowed for this master. // For now, we'll just enforce it's the same group. if group != masterModel.Group { c.JSON(http.StatusForbidden, gin.H{"error": "cannot issue key for a different group"}) return } key, rawChildKey, err := h.masterService.IssueChildKey(masterModel.ID, group, req.Scopes) if err != nil { switch { case errors.Is(err, service.ErrMasterNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrMasterNotActive): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrChildKeyGroupForbidden): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, service.ErrChildKeyLimitReached): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to issue child key", "details": err.Error()}) } return } if err := h.syncService.SyncKey(key); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync child key", "details": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "id": key.ID, "key_secret": rawChildKey, "group": key.Group, "scopes": key.Scopes, }) }