Files
llama-link/internal/api/handlers/keys.go
T
darroyo 4c9ed3c24b Initial commit: LlamaLink Go rewrite
Complete rewrite from Python/FastAPI to Go/Gin:
- Go backend: auth (API keys + bcrypt), llama.cpp subprocess manager,
  hot-swap multi-model, rate limiting, quota system, webhooks
- Vue 3 SPA admin panel (src/) with Tailwind CSS
- Deployment: Docker multi-stage, docker-compose, nginx, systemd
- GORM/SQLite models: ApiKey, Model, UsageLog, Quota, Webhook
- REST API: /api/v1/admin/* (keys, models, chat, usage, health)
- Embedded frontend via go:embed (build output at web/dist/)

Removed legacy Python artifacts (app/, tests/, pyproject.toml, etc.)
2026-07-30 10:58:55 -04:00

123 lines
2.8 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/llamalink/llamalink/internal/api/middleware"
"github.com/llamalink/llamalink/internal/auth"
)
type KeysHandler struct {
authService *auth.Service
}
func NewKeysHandler(authService *auth.Service) *KeysHandler {
return &KeysHandler{authService: authService}
}
type CreateKeyRequest struct {
Name string `json:"name" binding:"required"`
Scopes []string `json:"scopes"`
TokensLimit *int `json:"tokens_limit"`
WebhookURL *string `json:"webhook_url"`
OwnerLabel *string `json:"owner_label"`
}
func (h *KeysHandler) ListKeys(c *gin.Context) {
includeInactive := c.Query("include_inactive") == "true"
keys, err := h.authService.List(includeInactive)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Don't expose key hash
safeKeys := make([]gin.H, len(keys))
for i, k := range keys {
safeKeys[i] = gin.H{
"id": k.ID,
"name": k.Name,
"key_prefix": k.KeyPrefix,
"scopes": k.Scopes,
"is_active": k.IsActive,
"is_admin": k.IsAdmin,
"owner_label": k.OwnerLabel,
"created_at": k.CreatedAt,
"last_used_at": k.LastUsedAt,
}
}
c.JSON(http.StatusOK, safeKeys)
}
func (h *KeysHandler) CreateKey(c *gin.Context) {
var req CreateKeyRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Scopes == nil {
req.Scopes = []string{"chat", "models", "usage"}
}
apiKey, rawKey, err := h.authService.Create(auth.CreateKeyRequest{
Name: req.Name,
Scopes: req.Scopes,
TokensLimit: req.TokensLimit,
WebhookURL: req.WebhookURL,
OwnerLabel: req.OwnerLabel,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": apiKey.ID,
"name": apiKey.Name,
"key": rawKey,
"key_prefix": apiKey.KeyPrefix,
"scopes": apiKey.Scopes,
"is_admin": apiKey.IsAdmin,
"created_at": apiKey.CreatedAt,
})
}
func (h *KeysHandler) RevokeKey(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid key id"})
return
}
// Can't revoke own key
currentKey := middleware.GetAPIKey(c)
if currentKey.ID == id {
c.JSON(http.StatusBadRequest, gin.H{
"error": gin.H{
"code": "validation_error",
"message": "Cannot revoke your own admin key",
},
})
return
}
if err := h.authService.Revoke(id); err != nil {
if err == auth.ErrKeyNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "key not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}