package admin import ( "log/slog" "net" "net/http" "sync" "time" "git.asio.asia/nixevol/NixMsg/internal/app/group" "git.asio.asia/nixevol/NixMsg/internal/app/identity" "git.asio.asia/nixevol/NixMsg/internal/auth" "git.asio.asia/nixevol/NixMsg/internal/config" "git.asio.asia/nixevol/NixMsg/internal/store" ) const ( cookieName = "nixmsg_admin" csrfHeader = "X-Nixmsg-Request" csrfValue = "1" adminUsername = "admin" settingAdminHash = "admin_password_hash" defaultSessionTTL = 12 * time.Hour minPasswordLen = 12 lastUsedMinGap = time.Minute ) // Deps 是管理 Handler 的依赖。 type Deps struct { DB *store.DB Hash auth.HashPool Tokens auth.APITokens Locks auth.LoginLocks Logger *slog.Logger // TrustedProxies 受信任代理网段。 TrustedProxies []*net.IPNet // SessionTTL 会话有效期;零值用 12 小时。 SessionTTL time.Duration // SecureCookies 为 true 时 Cookie 始终带 Secure;否则按请求是否 HTTPS 决定。 SecureCookies bool // KickEndpoint 踢下线钩子(只断开连接);nil 时踢线为 no-op。 KickEndpoint EndpointKickFunc // Identity 端停用/启用/删除级联(I5);nil 时回退为仅改 enabled/删行。 Identity identity.Service // Groups 群服务;nil 时群写操作返回 busy。列表/详情可只读库。 Groups group.Service // Config 只读运行参数来源;零值用 config.Default()。 Config config.Config // Version 概览里的版本号;空则 "dev"。 Version string } // Handler 是可挂载的管理接口(路由前缀 /api/admin/)。 type Handler struct { db *store.DB hash auth.HashPool tokens auth.APITokens locks auth.LoginLocks log *slog.Logger trusted []*net.IPNet ttl time.Duration forceSec bool kick EndpointKickFunc identity identity.Service groups group.Service cfg config.Config version string startedAt time.Time mux *http.ServeMux lastUsedMu sync.Mutex lastUsed map[string]time.Time // api token id -> last DB write } // New 构造可挂载的管理 Handler。返回值实现 http.Handler。 func New(d Deps) *Handler { if d.Logger == nil { d.Logger = slog.Default() } if d.Locks == nil { d.Locks = NewMemoryLoginLocks() } ttl := d.SessionTTL if ttl <= 0 { ttl = defaultSessionTTL } cfg := d.Config if cfg.Listen == "" { cfg = config.Default() } ver := d.Version if ver == "" { ver = "dev" } h := &Handler{ db: d.DB, hash: d.Hash, tokens: d.Tokens, locks: d.Locks, log: d.Logger, trusted: d.TrustedProxies, ttl: ttl, forceSec: d.SecureCookies, kick: d.KickEndpoint, identity: d.Identity, groups: d.Groups, cfg: cfg, version: ver, startedAt: time.Now(), mux: http.NewServeMux(), lastUsed: make(map[string]time.Time), } h.routes() return h } // ServeHTTP 实现 http.Handler。 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) } func (h *Handler) routes() { // 公开 h.mux.HandleFunc("POST /api/admin/login", h.handleLogin) // 需鉴权 h.mux.Handle("POST /api/admin/logout", h.auth(h.handleLogout)) h.mux.Handle("GET /api/admin/me", h.auth(h.handleMe)) h.mux.Handle("POST /api/admin/password", h.auth(h.handlePassword)) // 令牌管理:仅 Cookie h.mux.Handle("GET /api/admin/tokens", h.auth(h.handleTokenList)) h.mux.Handle("POST /api/admin/tokens", h.auth(h.handleTokenCreate)) h.mux.Handle("PATCH /api/admin/tokens/{id}", h.auth(h.handleTokenPatch)) h.mux.Handle("DELETE /api/admin/tokens/{id}", h.auth(h.handleTokenDelete)) // A2 端管理 h.mux.Handle("GET /api/admin/endpoints", h.auth(h.handleEndpointList)) h.mux.Handle("POST /api/admin/endpoints", h.auth(h.handleEndpointCreate)) h.mux.Handle("POST /api/admin/endpoints/import", h.auth(h.handleEndpointImport)) h.mux.Handle("POST /api/admin/endpoints/batch", h.auth(h.handleEndpointBatch)) h.mux.Handle("GET /api/admin/endpoints/{id}", h.auth(h.handleEndpointGet)) h.mux.Handle("PATCH /api/admin/endpoints/{id}", h.auth(h.handleEndpointPatch)) h.mux.Handle("DELETE /api/admin/endpoints/{id}", h.auth(h.handleEndpointDelete)) h.mux.Handle("POST /api/admin/endpoints/{id}/kick", h.auth(h.handleEndpointKick)) h.mux.Handle("POST /api/admin/endpoints/{id}/reset-login-password", h.auth(h.handleEndpointResetLoginPassword)) h.mux.Handle("PUT /api/admin/endpoints/{id}/talk-password", h.auth(h.handleEndpointTalkPassword)) h.mux.Handle("POST /api/admin/endpoints/{id}/unlock", h.auth(h.handleEndpointUnlock)) // A3 h.mux.Handle("GET /api/admin/overview", h.auth(h.handleOverview)) h.mux.Handle("GET /api/admin/registration", h.auth(h.handleRegistrationGet)) h.mux.Handle("PUT /api/admin/registration", h.auth(h.handleRegistrationPut)) h.mux.Handle("GET /api/admin/groups", h.auth(h.handleGroupList)) h.mux.Handle("POST /api/admin/groups", h.auth(h.handleGroupCreate)) h.mux.Handle("GET /api/admin/groups/{id}", h.auth(h.handleGroupGet)) h.mux.Handle("PATCH /api/admin/groups/{id}", h.auth(h.handleGroupRename)) h.mux.Handle("DELETE /api/admin/groups/{id}", h.auth(h.handleGroupDissolve)) h.mux.Handle("POST /api/admin/groups/{id}/members", h.auth(h.handleGroupAddMembers)) h.mux.Handle("DELETE /api/admin/groups/{id}/members/{endpointId}", h.auth(h.handleGroupRemoveMember)) h.mux.Handle("POST /api/admin/groups/{id}/transfer", h.auth(h.handleGroupTransfer)) h.mux.Handle("GET /api/admin/messages", h.auth(h.handleMessageList)) h.mux.Handle("GET /api/admin/messages/{seq}", h.auth(h.handleMessageGet)) h.mux.Handle("GET /api/admin/settings", h.auth(h.handleSettingsGet)) }