4d34c6d31a
CI / test (push) Failing after 12m45s
- 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
110 lines
2.5 KiB
Go
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)
|
|
}
|