package admin import ( "crypto/rand" "database/sql" "encoding/hex" "errors" "net/http" "strings" "git.asio.asia/nixevol/NixMsg/internal/httpx" ) func (h *Handler) handleTokenList(w http.ResponseWriter, r *http.Request) { items, err := h.listAPITokens(r.Context()) if err != nil { httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } out := make([]map[string]any, 0, len(items)) for _, it := range items { row := map[string]any{ "id": it.ID, "name": it.Name, "enabled": it.Enabled, "created_at_ms": it.CreatedAt.UnixMilli(), "last_used_at_ms": nil, } if it.LastUsedAt != nil { row["last_used_at_ms"] = it.LastUsedAt.UnixMilli() } out = append(out, row) } httpx.WriteOK(w, map[string]any{ "items": out, "next_cursor": "", "total": len(out), }) } func (h *Handler) handleTokenCreate(w http.ResponseWriter, r *http.Request) { p, _ := principalFrom(r.Context()) ip := httpx.ClientIP(r, h.trusted) var req struct { Name string `json:"name"` } if err := httpx.DecodeJSON(r, &req); err != nil || strings.TrimSpace(req.Name) == "" { h.audit(actorString(p), "token_create", "", "bad_request", ip) httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称不能为空") return } name := strings.TrimSpace(req.Name) plain, hash, err := h.tokens.Issue(r.Context()) if err != nil { h.audit(actorString(p), "token_create", "", "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } id, err := newTokenID() if err != nil { h.audit(actorString(p), "token_create", "", "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } created, err := h.insertAPIToken(r.Context(), id, name, hash) if err != nil { h.audit(actorString(p), "token_create", id, "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } h.audit(actorString(p), "token_create", id, "ok", ip) httpx.WriteOK(w, map[string]any{ "id": id, "name": name, "token": plain, "created_at_ms": created.UnixMilli(), }) } func (h *Handler) handleTokenPatch(w http.ResponseWriter, r *http.Request) { p, _ := principalFrom(r.Context()) ip := httpx.ClientIP(r, h.trusted) id := r.PathValue("id") var req struct { Name *string `json:"name"` Enabled *bool `json:"enabled"` } if err := httpx.DecodeJSON(r, &req); err != nil { h.audit(actorString(p), "token_update", id, "bad_request", ip) httpx.WriteError(w, http.StatusBadRequest, "bad_request", "请求体无效") return } if req.Name == nil && req.Enabled == nil { h.audit(actorString(p), "token_update", id, "bad_request", ip) httpx.WriteError(w, http.StatusBadRequest, "bad_request", "无更新字段") return } if req.Name != nil { n := strings.TrimSpace(*req.Name) if n == "" { h.audit(actorString(p), "token_update", id, "bad_request", ip) httpx.WriteError(w, http.StatusBadRequest, "bad_request", "名称不能为空") return } req.Name = &n } if err := h.updateAPIToken(r.Context(), id, req.Name, req.Enabled); err != nil { if errors.Is(err, sql.ErrNoRows) { h.audit(actorString(p), "token_update", id, "not_found", ip) httpx.WriteError(w, http.StatusNotFound, "not_found", "令牌不存在") return } h.audit(actorString(p), "token_update", id, "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } row, err := h.getAPITokenByID(r.Context(), id) if err != nil { h.audit(actorString(p), "token_update", id, "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } h.audit(actorString(p), "token_update", id, "ok", ip) resp := map[string]any{ "id": row.ID, "name": row.Name, "enabled": row.Enabled, "created_at_ms": row.CreatedAt.UnixMilli(), "last_used_at_ms": nil, } if row.LastUsedAt != nil { resp["last_used_at_ms"] = row.LastUsedAt.UnixMilli() } httpx.WriteOK(w, resp) } func (h *Handler) handleTokenDelete(w http.ResponseWriter, r *http.Request) { p, _ := principalFrom(r.Context()) ip := httpx.ClientIP(r, h.trusted) id := r.PathValue("id") if err := h.deleteAPIToken(r.Context(), id); err != nil { if errors.Is(err, sql.ErrNoRows) { h.audit(actorString(p), "token_delete", id, "not_found", ip) httpx.WriteError(w, http.StatusNotFound, "not_found", "令牌不存在") return } h.audit(actorString(p), "token_delete", id, "error", ip) httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } h.audit(actorString(p), "token_delete", id, "ok", ip) httpx.WriteOK(w, map[string]any{}) } func newTokenID() (string, error) { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil }