feat(keys): role-based gateway keys with admin management UI and per-user model scope

This commit is contained in:
root
2026-08-09 10:01:40 +08:00
parent dec03238dd
commit 3408c9cb1f
10 changed files with 901 additions and 82 deletions

View File

@ -4,9 +4,12 @@
package core
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"time"
"llmsproxy/internal/config"
"llmsproxy/internal/lua"
@ -44,12 +47,47 @@ func NewFromConfig(cfg *config.Config) (*Core, error) {
return nil, fmt.Errorf("runtime store: %w", err)
}
c.scheduler = scheduler.New(buildRetries(cfg))
if err := c.seedKeys(); err != nil {
return nil, err
}
if err := c.rebuildRegistry(); err != nil {
return nil, err
}
return c, nil
}
// seedKeys migrates the static config gateway_keys into the runtime store as
// admin keys (once), so later UI-created keys can share the same store.
func (c *Core) seedKeys() error {
existing := map[string]bool{}
for _, k := range c.store.ListKeys() {
existing[k.Key] = true
}
changed := false
for i, raw := range c.cfg.GatewayKeys {
if raw == "" || existing[raw] {
continue
}
name := "admin"
if i > 0 {
name = fmt.Sprintf("admin-%d", i+1)
}
if err := c.store.SaveKey(config.GWKey{
Key: raw,
Role: "admin",
Name: name,
CreatedAt: time.Now().Unix(),
}); err != nil {
return err
}
changed = true
}
if changed {
return c.store.Load()
}
return nil
}
func buildRetries(cfg *config.Config) int {
return len(cfg.Sources) // allow fallback across all sources
}
@ -67,6 +105,60 @@ func (c *Core) GatewayKeys() []string { return c.cfg.GatewayKeys }
func (c *Core) Listen() string { return c.cfg.Listen }
// ---- gateway key management (web UI) ----
// ListKeys returns all gateway keys (admin view).
func (c *Core) ListKeys() []config.GWKey { return c.store.ListKeys() }
// FindKey looks up a gateway key record by its secret value.
func (c *Core) FindKey(key string) (config.GWKey, bool) { return c.store.KeyByValue(key) }
// CreateKey builds a new random gateway key and persists it.
func (c *Core) CreateKey(name, role string, models []string, note string) (config.GWKey, error) {
key := make([]byte, 16)
if _, err := rand.Read(key); err != nil {
return config.GWKey{}, err
}
rec := config.GWKey{
Key: "sk-gw-" + hex.EncodeToString(key),
Role: role,
Name: name,
Models: models,
Note: note,
CreatedAt: time.Now().Unix(),
}
if rec.Role == "" {
rec.Role = "user"
}
if err := c.store.SaveKey(rec); err != nil {
return config.GWKey{}, err
}
return rec, nil
}
// UpdateKey mutates a key's name/role/model scope and persists it.
func (c *Core) UpdateKey(key, name, role string, models []string, note string) (config.GWKey, error) {
rec, ok := c.store.KeyByValue(key)
if !ok {
return config.GWKey{}, fmt.Errorf("key not found")
}
if name != "" {
rec.Name = name
}
if role == "admin" || role == "user" {
rec.Role = role
}
rec.Models = models
rec.Note = note
if err := c.store.SaveKey(rec); err != nil {
return config.GWKey{}, err
}
return rec, nil
}
// DeleteKey removes a key record; returns false if it did not exist.
func (c *Core) DeleteKey(key string) (bool, error) { return c.store.DeleteKey(key) }
// Config exposes the underlying configuration (read-only usage).
func (c *Core) Config() *config.Config { return c.cfg }