Files
darroyo 4d34c6d31a
CI / test (push) Failing after 12m45s
Replace token-based admin auth with JWT session authentication
- Add AdminUser model (bcrypt hashed passwords) and admin_users table
- Add AdminJWTService for HS256 JWT sessions (24h TTL)
- Add AdminSessionAuth middleware for /api/v1/admin/* routes
- Add admin handlers: login, logout, me, change-password, users CRUD
- Keys and model management routes now require admin JWT session
- Remove ADMIN_TOKEN, add ADMIN_USERNAME, ADMIN_PASSWORD env vars
- Update frontend: username/password login, admin_session storage, AdminUsers CRUD view
2026-07-31 17:15:29 -04:00

110 lines
2.5 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"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
}
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)
}