feat: 支持 API Key 类型与管理接口认证
This commit is contained in:
1 parent
e952a5a67f
commit
52a30aae74
7 files changed
+233
-11
No files matched your search
@@ -25,7 +25,7 @@ func (h *AgentHandler) DownloadAgent(c echo.Context) error {
|
||||
|
||||
// 校验 API Key
|
||||
apiKey := c.QueryParam("key")
|
||||
if _, err := h.apiKeyService.ValidateApiKey(c.Request().Context(), apiKey); err != nil {
|
||||
if _, err := h.apiKeyService.ValidateApiKey(c.Request().Context(), apiKey, "agent"); err != nil {
|
||||
// API Key 校验失败,尝试 IP 白名单兜底(兼容旧版 Agent 自动更新)
|
||||
clientIP := c.RealIP()
|
||||
isOnline, checkErr := h.agentService.IsAgentByIP(c.Request().Context(), clientIP)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dushixiang/pika/internal/service"
|
||||
"github.com/go-orz/orz"
|
||||
"github.com/labstack/echo/v4"
|
||||
@@ -22,6 +24,7 @@ func NewApiKeyHandler(logger *zap.Logger, apiKeyService *service.ApiKeyService)
|
||||
// GenerateApiKeyRequest 生成API密钥请求
|
||||
type GenerateApiKeyRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// UpdateApiKeyNameRequest 更新API密钥名称请求
|
||||
@@ -32,6 +35,7 @@ type UpdateApiKeyNameRequest struct {
|
||||
// Paging API密钥分页查询
|
||||
func (r ApiKeyHandler) Paging(c echo.Context) error {
|
||||
name := c.QueryParam("name")
|
||||
apiKeyType := c.QueryParam("type")
|
||||
|
||||
pr := orz.GetPageRequest(c, "created_at", "name")
|
||||
|
||||
@@ -39,18 +43,31 @@ func (r ApiKeyHandler) Paging(c echo.Context) error {
|
||||
PageRequest(pr).
|
||||
Contains("name", name)
|
||||
|
||||
if apiKeyType != "" {
|
||||
builder = builder.Equal("type", apiKeyType)
|
||||
}
|
||||
|
||||
ctx := c.Request().Context()
|
||||
page, err := builder.Execute(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回完整密钥,由前端控制显示/隐藏
|
||||
// 遮蔽密钥值,完整密钥仅在创建时返回一次
|
||||
for i := range page.Items {
|
||||
page.Items[i].Key = maskApiKey(page.Items[i].Key)
|
||||
}
|
||||
|
||||
return orz.Ok(c, page)
|
||||
}
|
||||
|
||||
// Create 生成API密钥
|
||||
// Create 生成API密钥(仅限通信密钥 type=agent)
|
||||
func (r ApiKeyHandler) Create(c echo.Context) error {
|
||||
// API Key 不能创建其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能创建其他 API Key")
|
||||
}
|
||||
|
||||
var req GenerateApiKeyRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return err
|
||||
@@ -62,8 +79,11 @@ func (r ApiKeyHandler) Create(c echo.Context) error {
|
||||
// 从上下文获取用户ID
|
||||
userID := c.Get("userID").(string)
|
||||
|
||||
// 固定类型为 agent(通信密钥)
|
||||
apiKeyType := "agent"
|
||||
|
||||
ctx := c.Request().Context()
|
||||
apiKey, err := r.apiKeyService.GenerateApiKey(ctx, req.Name, userID)
|
||||
apiKey, err := r.apiKeyService.GenerateApiKey(ctx, req.Name, userID, apiKeyType)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to generate api key", zap.Error(err))
|
||||
return err
|
||||
@@ -72,8 +92,40 @@ func (r ApiKeyHandler) Create(c echo.Context) error {
|
||||
return orz.Ok(c, apiKey)
|
||||
}
|
||||
|
||||
// CreateAdmin 生成管理 API Key(type=admin,完整 key 仅创建时返回一次)
|
||||
func (r ApiKeyHandler) CreateAdmin(c echo.Context) error {
|
||||
// API Key 不能创建其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能创建其他 API Key")
|
||||
}
|
||||
|
||||
var req GenerateApiKeyRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userID := c.Get("userID").(string)
|
||||
|
||||
ctx := c.Request().Context()
|
||||
apiKey, err := r.apiKeyService.GenerateApiKey(ctx, req.Name, userID, "admin")
|
||||
if err != nil {
|
||||
r.logger.Error("failed to generate admin api key", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return orz.Ok(c, apiKey)
|
||||
}
|
||||
|
||||
// Get 获取API密钥详情
|
||||
func (r ApiKeyHandler) Get(c echo.Context) error {
|
||||
// API Key 不能查看其他 API Key 的完整值
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能查看其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
ctx := c.Request().Context()
|
||||
|
||||
@@ -83,12 +135,44 @@ func (r ApiKeyHandler) Get(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回完整密钥,由前端控制显示/隐藏
|
||||
// 遮蔽密钥值
|
||||
apiKey.Key = maskApiKey(apiKey.Key)
|
||||
return orz.Ok(c, apiKey)
|
||||
}
|
||||
|
||||
// GetRaw 获取API密钥完整值(仅在生成安装命令等场景使用)
|
||||
func (r ApiKeyHandler) GetRaw(c echo.Context) error {
|
||||
// 仅 JWT 认证可用
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能查看其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
ctx := c.Request().Context()
|
||||
|
||||
apiKey, err := r.apiKeyService.GetApiKey(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to get api key", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// admin 类型密钥永远不可查看
|
||||
if apiKey.Type == "admin" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "管理 API Key 不允许查看完整值")
|
||||
}
|
||||
|
||||
return orz.Ok(c, orz.Map{
|
||||
"key": apiKey.Key,
|
||||
})
|
||||
}
|
||||
|
||||
// Update 更新API密钥名称
|
||||
func (r ApiKeyHandler) Update(c echo.Context) error {
|
||||
// API Key 不能修改其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能修改其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
|
||||
var req UpdateApiKeyNameRequest
|
||||
@@ -110,6 +194,11 @@ func (r ApiKeyHandler) Update(c echo.Context) error {
|
||||
|
||||
// Delete 删除API密钥
|
||||
func (r ApiKeyHandler) Delete(c echo.Context) error {
|
||||
// API Key 不能删除其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能删除其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
ctx := c.Request().Context()
|
||||
|
||||
@@ -123,6 +212,11 @@ func (r ApiKeyHandler) Delete(c echo.Context) error {
|
||||
|
||||
// Enable 启用API密钥
|
||||
func (r ApiKeyHandler) Enable(c echo.Context) error {
|
||||
// API Key 不能启用/禁用其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能启用/禁用其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
ctx := c.Request().Context()
|
||||
|
||||
@@ -136,6 +230,11 @@ func (r ApiKeyHandler) Enable(c echo.Context) error {
|
||||
|
||||
// Disable 禁用API密钥
|
||||
func (r ApiKeyHandler) Disable(c echo.Context) error {
|
||||
// API Key 不能启用/禁用其他 API Key
|
||||
if c.Get("authType") == "api_key" {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "API Key 不能启用/禁用其他 API Key")
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
ctx := c.Request().Context()
|
||||
|
||||
@@ -146,3 +245,11 @@ func (r ApiKeyHandler) Disable(c echo.Context) error {
|
||||
|
||||
return orz.Ok(c, orz.Map{})
|
||||
}
|
||||
|
||||
// maskApiKey 遮蔽API密钥,只显示前4位和后4位
|
||||
func maskApiKey(key string) string {
|
||||
if len(key) <= 12 {
|
||||
return "****"
|
||||
}
|
||||
return key[:4] + "..." + key[len(key)-4:]
|
||||
}
|
||||
Reference in new issue
Block a user