package api import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "ws_usernode/internal/config" "ws_usernode/internal/model" "ws_usernode/internal/service" ) // KeyHandler SSH 公钥接口:外部用户自助管理(/me/keys)+ 管理员查看(/users/:id/keys)。 // 密钥仅用户上传(管理员不代签);吊销后立即从 authorized_keys 移除。 type KeyHandler struct { svc *service.KeyService cfg *config.Config h *Handler // 访问审计 helper } // KeyCreateRequest 上传公钥。 type KeyCreateRequest struct { Name string `json:"name" binding:"required"` // 显示名称,1~64 字符 PublicKey string `json:"public_key" binding:"required"` } // KeyRenameRequest 重命名。 type KeyRenameRequest struct { Name string `json:"name" binding:"required"` } // currentUserID 从会话取当前外部用户 ID(/me/keys 均为 user 会话)。 func (h *KeyHandler) currentUserID(c *gin.Context) uint { if sess := sessionFrom(c); sess != nil { return sess.RefID } return 0 } // ListMine GET /me/keys —— 我的密钥列表。 func (h *KeyHandler) ListMine(c *gin.Context) { keys, err := h.svc.ListByUser(c.Request.Context(), h.currentUserID(c)) if err != nil { fail(c, http.StatusInternalServerError, err.Error()) return } ok(c, gin.H{"items": keys}) } // Create POST /me/keys —— 上传公钥(类型/长度/重复校验 + 同步 authorized_keys)。 func (h *KeyHandler) Create(c *gin.Context) { var req KeyCreateRequest if err := c.ShouldBindJSON(&req); err != nil { fail(c, http.StatusBadRequest, "请求参数不合法: "+err.Error()) return } uid := h.currentUserID(c) k, err := h.svc.Create(c.Request.Context(), uid, req.Name, req.PublicKey, uid) if err != nil { h.h.audit(c, "key.create", "ssh_key", "", map[string]any{"name": req.Name, "err": err.Error()}, model.ResultFailed) switch { case errors.Is(err, service.ErrKeyInvalid): fail(c, http.StatusBadRequest, err.Error()) case errors.Is(err, service.ErrKeyDuplicate): fail(c, http.StatusConflict, err.Error()) case errors.Is(err, service.ErrUserNotFound): fail(c, http.StatusNotFound, err.Error()) case errors.Is(err, service.ErrUserNotActive): fail(c, http.StatusConflict, err.Error()) default: fail(c, http.StatusInternalServerError, err.Error()) } return } h.h.audit(c, "key.create", "ssh_key", strconv.FormatUint(uint64(k.ID), 10), map[string]any{"name": k.Name, "fingerprint": k.Fingerprint}, model.ResultSuccess) ok(c, k) } // Rename PATCH /me/keys/:id —— 重命名(仅元数据,不影响 authorized_keys)。 func (h *KeyHandler) Rename(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { fail(c, http.StatusBadRequest, "无效的密钥 ID") return } var req KeyRenameRequest if err := c.ShouldBindJSON(&req); err != nil { fail(c, http.StatusBadRequest, "请求参数不合法: "+err.Error()) return } k, err := h.svc.Rename(c.Request.Context(), uint(id), h.currentUserID(c), req.Name) if err != nil { h.h.audit(c, "key.rename", "ssh_key", c.Param("id"), map[string]any{"err": err.Error()}, model.ResultFailed) switch { case errors.Is(err, service.ErrKeyNotFound): fail(c, http.StatusNotFound, err.Error()) case errors.Is(err, service.ErrKeyInvalid): fail(c, http.StatusBadRequest, err.Error()) default: fail(c, http.StatusInternalServerError, err.Error()) } return } h.h.audit(c, "key.rename", "ssh_key", c.Param("id"), map[string]any{"name": k.Name}, model.ResultSuccess) ok(c, k) } // Revoke DELETE /me/keys/:id —— 吊销(从 authorized_keys 移除,立即失效)。 func (h *KeyHandler) Revoke(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { fail(c, http.StatusBadRequest, "无效的密钥 ID") return } k, err := h.svc.Revoke(c.Request.Context(), uint(id), h.currentUserID(c)) if err != nil { h.h.audit(c, "key.revoke", "ssh_key", c.Param("id"), map[string]any{"err": err.Error()}, model.ResultFailed) switch { case errors.Is(err, service.ErrKeyNotFound): fail(c, http.StatusNotFound, err.Error()) default: fail(c, http.StatusInternalServerError, err.Error()) } return } h.h.audit(c, "key.revoke", "ssh_key", c.Param("id"), map[string]any{"fingerprint": k.Fingerprint}, model.ResultSuccess) ok(c, k) } // ListForUser GET /users/:id/keys —— 管理员查看用户密钥。 func (h *KeyHandler) ListForUser(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { fail(c, http.StatusBadRequest, "无效的用户 ID") return } keys, err := h.svc.ListByUser(c.Request.Context(), uint(id)) if err != nil { switch { case errors.Is(err, service.ErrUserNotFound): fail(c, http.StatusNotFound, err.Error()) default: fail(c, http.StatusInternalServerError, err.Error()) } return } ok(c, gin.H{"items": keys}) }