mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 18:13:04 +00:00
209 lines
6.9 KiB
Go
209 lines
6.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// IssueAPIKeyHandler issues an API key after signature verification.
|
|
// Similar to VerifyHandler but only returns the API key without JWT tokens.
|
|
// For non-default namespaces, may trigger cluster provisioning and return 202 Accepted.
|
|
//
|
|
// POST /v1/auth/api-key
|
|
// Request body: APIKeyRequest
|
|
// Response: { "api_key", "namespace", "plan", "wallet" }
|
|
// Or 202 Accepted: { "status": "provisioning", "cluster_id", "poll_url" }
|
|
func (h *Handlers) IssueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
|
if h.authService == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "auth service not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req APIKeyRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Wallet) == "" || strings.TrimSpace(req.Nonce) == "" || strings.TrimSpace(req.Signature) == "" {
|
|
writeError(w, http.StatusBadRequest, "wallet, nonce and signature are required")
|
|
return
|
|
}
|
|
|
|
ctx := r.Context()
|
|
verified, err := h.authService.VerifySignature(ctx, req.Wallet, req.Nonce, req.Signature, req.ChainType)
|
|
if err != nil || !verified {
|
|
writeError(w, http.StatusUnauthorized, "signature verification failed")
|
|
return
|
|
}
|
|
|
|
// Mark nonce used
|
|
nsID, _ := h.resolveNamespace(ctx, req.Namespace)
|
|
h.markNonceUsed(ctx, nsID, strings.ToLower(req.Wallet), req.Nonce)
|
|
|
|
// Check if namespace cluster provisioning is needed (for non-default namespaces)
|
|
namespace := strings.TrimSpace(req.Namespace)
|
|
if namespace == "" {
|
|
namespace = "default"
|
|
}
|
|
|
|
if h.clusterProvisioner != nil && namespace != "default" {
|
|
clusterID, status, needsProvisioning, err := h.clusterProvisioner.CheckNamespaceCluster(ctx, namespace)
|
|
if err != nil {
|
|
// Log but don't fail - cluster provisioning is optional (error may just mean no cluster yet)
|
|
_ = err
|
|
} else if needsProvisioning {
|
|
// Trigger provisioning for new namespace
|
|
nsIDInt := 0
|
|
if id, ok := nsID.(int); ok {
|
|
nsIDInt = id
|
|
} else if id, ok := nsID.(int64); ok {
|
|
nsIDInt = int(id)
|
|
} else if id, ok := nsID.(float64); ok {
|
|
nsIDInt = int(id)
|
|
}
|
|
|
|
newClusterID, pollURL, provErr := h.clusterProvisioner.ProvisionNamespaceCluster(ctx, nsIDInt, namespace, req.Wallet)
|
|
if provErr != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to start cluster provisioning")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusAccepted, map[string]any{
|
|
"status": "provisioning",
|
|
"cluster_id": newClusterID,
|
|
"poll_url": pollURL,
|
|
"estimated_time_seconds": 60,
|
|
"message": "Namespace cluster is being provisioned. Poll the status URL for updates.",
|
|
})
|
|
return
|
|
} else if status == "provisioning" {
|
|
// Already provisioning, return poll URL
|
|
writeJSON(w, http.StatusAccepted, map[string]any{
|
|
"status": "provisioning",
|
|
"cluster_id": clusterID,
|
|
"poll_url": "/v1/namespace/status?id=" + clusterID,
|
|
"estimated_time_seconds": 60,
|
|
"message": "Namespace cluster is being provisioned. Poll the status URL for updates.",
|
|
})
|
|
return
|
|
}
|
|
// If status is "ready" or "default", proceed with API key generation
|
|
}
|
|
|
|
apiKey, err := h.authService.GetOrCreateAPIKey(ctx, req.Wallet, req.Namespace)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"api_key": apiKey,
|
|
"namespace": req.Namespace,
|
|
"plan": func() string {
|
|
if strings.TrimSpace(req.Plan) == "" {
|
|
return "free"
|
|
}
|
|
return req.Plan
|
|
}(),
|
|
"wallet": strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(req.Wallet, "0x"), "0X")),
|
|
})
|
|
}
|
|
|
|
// SimpleAPIKeyHandler generates an API key without signature verification.
|
|
// This is a simplified flow for development/testing purposes.
|
|
//
|
|
// POST /v1/auth/simple-key
|
|
// Request body: SimpleAPIKeyRequest
|
|
// Response: { "api_key", "namespace", "wallet", "created" }
|
|
func (h *Handlers) SimpleAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
|
if h.authService == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "auth service not initialized")
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req SimpleAPIKeyRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Wallet) == "" {
|
|
writeError(w, http.StatusBadRequest, "wallet is required")
|
|
return
|
|
}
|
|
|
|
// Check if namespace cluster provisioning is needed (for non-default namespaces)
|
|
namespace := strings.TrimSpace(req.Namespace)
|
|
if namespace == "" {
|
|
namespace = "default"
|
|
}
|
|
|
|
ctx := r.Context()
|
|
if h.clusterProvisioner != nil && namespace != "default" {
|
|
clusterID, status, needsProvisioning, err := h.clusterProvisioner.CheckNamespaceCluster(ctx, namespace)
|
|
if err != nil {
|
|
// Log but don't fail - cluster provisioning is optional
|
|
_ = err
|
|
} else if needsProvisioning {
|
|
// Trigger provisioning for new namespace
|
|
nsID, _ := h.resolveNamespace(ctx, namespace)
|
|
nsIDInt := 0
|
|
if id, ok := nsID.(int); ok {
|
|
nsIDInt = id
|
|
} else if id, ok := nsID.(int64); ok {
|
|
nsIDInt = int(id)
|
|
} else if id, ok := nsID.(float64); ok {
|
|
nsIDInt = int(id)
|
|
}
|
|
|
|
newClusterID, pollURL, provErr := h.clusterProvisioner.ProvisionNamespaceCluster(ctx, nsIDInt, namespace, req.Wallet)
|
|
if provErr != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to start cluster provisioning")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusAccepted, map[string]any{
|
|
"status": "provisioning",
|
|
"cluster_id": newClusterID,
|
|
"poll_url": pollURL,
|
|
"estimated_time_seconds": 60,
|
|
"message": "Namespace cluster is being provisioned. Poll the status URL for updates.",
|
|
})
|
|
return
|
|
} else if status == "provisioning" {
|
|
// Already provisioning, return poll URL
|
|
writeJSON(w, http.StatusAccepted, map[string]any{
|
|
"status": "provisioning",
|
|
"cluster_id": clusterID,
|
|
"poll_url": "/v1/namespace/status?id=" + clusterID,
|
|
"estimated_time_seconds": 60,
|
|
"message": "Namespace cluster is being provisioned. Poll the status URL for updates.",
|
|
})
|
|
return
|
|
}
|
|
// If status is "ready" or "default", proceed with API key generation
|
|
}
|
|
|
|
apiKey, err := h.authService.GetOrCreateAPIKey(ctx, req.Wallet, req.Namespace)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"api_key": apiKey,
|
|
"namespace": req.Namespace,
|
|
"wallet": strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(req.Wallet, "0x"), "0X")),
|
|
"created": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|