Changeset 0.9.4 (#54)
This commit is contained in:
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/crypto"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
@@ -19,10 +21,11 @@ import (
|
||||
|
||||
type AdminHandler struct {
|
||||
stores store.Stores
|
||||
vault *crypto.KeyResolver
|
||||
}
|
||||
|
||||
func NewAdminHandler(s store.Stores) *AdminHandler {
|
||||
return &AdminHandler{stores: s}
|
||||
func NewAdminHandler(s store.Stores, vault *crypto.KeyResolver) *AdminHandler {
|
||||
return &AdminHandler{stores: s, vault: vault}
|
||||
}
|
||||
|
||||
// ── User Management ─────────────────────────
|
||||
@@ -254,7 +257,7 @@ func (h *AdminHandler) ListGlobalConfigs(c *gin.Context) {
|
||||
for i, cfg := range cfgs {
|
||||
out[i] = configWithKey{
|
||||
ProviderConfig: cfg,
|
||||
HasKey: cfg.APIKeyEnc != "",
|
||||
HasKey: cfg.HasKey(),
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"configs": out})
|
||||
@@ -291,16 +294,32 @@ func (h *AdminHandler) CreateGlobalConfig(c *gin.Context) {
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
Endpoint: req.Endpoint,
|
||||
APIKeyEnc: req.APIKey, // TODO: encrypt
|
||||
ModelDefault: req.ModelDefault,
|
||||
Config: models.JSONMap(req.Config),
|
||||
Headers: models.JSONMap(req.Headers),
|
||||
Settings: models.JSONMap(req.Settings),
|
||||
Scope: models.ScopeGlobal,
|
||||
KeyScope: models.ScopeGlobal,
|
||||
IsActive: true,
|
||||
IsPrivate: req.IsPrivate,
|
||||
}
|
||||
|
||||
// Encrypt the API key
|
||||
if req.APIKey != "" {
|
||||
if h.vault != nil {
|
||||
enc, nonce, err := h.vault.EncryptForScope(req.APIKey, "global", "")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to encrypt API key"})
|
||||
return
|
||||
}
|
||||
cfg.APIKeyEnc = enc
|
||||
cfg.KeyNonce = nonce
|
||||
} else {
|
||||
// No vault configured — store raw bytes (unencrypted fallback)
|
||||
cfg.APIKeyEnc = []byte(req.APIKey)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.stores.Providers.Create(c.Request.Context(), cfg); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create config"})
|
||||
return
|
||||
@@ -319,7 +338,7 @@ func (h *AdminHandler) CreateGlobalConfig(c *gin.Context) {
|
||||
"settings": cfg.Settings,
|
||||
"is_active": cfg.IsActive,
|
||||
"is_private": cfg.IsPrivate,
|
||||
"has_key": cfg.APIKeyEnc != "",
|
||||
"has_key": cfg.HasKey(),
|
||||
"created_at": cfg.CreatedAt,
|
||||
"updated_at": cfg.UpdatedAt,
|
||||
})
|
||||
@@ -339,9 +358,19 @@ func (h *AdminHandler) UpdateGlobalConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
patch := req.ProviderConfigPatch
|
||||
// Transfer api_key → APIKeyEnc (json:"-" prevents auto-binding)
|
||||
// Transfer api_key → encrypted fields
|
||||
if req.APIKey != nil && *req.APIKey != "" {
|
||||
patch.APIKeyEnc = req.APIKey // TODO: encrypt
|
||||
if h.vault != nil {
|
||||
enc, nonce, err := h.vault.EncryptForScope(*req.APIKey, "global", "")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to encrypt API key"})
|
||||
return
|
||||
}
|
||||
patch.APIKeyEnc = enc
|
||||
patch.KeyNonce = nonce
|
||||
} else {
|
||||
patch.APIKeyEnc = []byte(*req.APIKey)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.stores.Providers.Update(c.Request.Context(), id, patch); err != nil {
|
||||
@@ -446,7 +475,21 @@ func (h *AdminHandler) FetchModels(c *gin.Context) {
|
||||
|
||||
// fetchModelsForProvider fetches and syncs models for a single provider config.
|
||||
func (h *AdminHandler) fetchModelsForProvider(c *gin.Context, cfg *models.ProviderConfig) (added, updated, total int, err error) {
|
||||
result, err := syncProviderModels(c.Request.Context(), h.stores, cfg)
|
||||
// Decrypt the API key for the provider API call
|
||||
apiKey := ""
|
||||
if len(cfg.APIKeyEnc) > 0 {
|
||||
if h.vault != nil {
|
||||
apiKey, err = h.vault.Decrypt(cfg.APIKeyEnc, cfg.KeyNonce, cfg.KeyScope, "")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to decrypt API key: %w", err)
|
||||
}
|
||||
} else {
|
||||
// No vault — key stored as raw bytes (unencrypted fallback)
|
||||
apiKey = string(cfg.APIKeyEnc)
|
||||
}
|
||||
}
|
||||
|
||||
result, err := syncProviderModels(c.Request.Context(), h.stores, cfg, apiKey)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user