166 lines
4.9 KiB
Go
166 lines
4.9 KiB
Go
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
|
|
}
|