feat: 实现管理鉴权、API 令牌与操作日志
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
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,
|
||||
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))
|
||||
|
||||
// 其余管理路由:鉴权生效,业务暂 501
|
||||
for _, p := range stubRoutes {
|
||||
h.mux.Handle(p, h.auth(h.handleNotImplemented))
|
||||
}
|
||||
}
|
||||
|
||||
var stubRoutes = []string{
|
||||
"GET /api/admin/overview",
|
||||
"GET /api/admin/endpoints",
|
||||
"POST /api/admin/endpoints",
|
||||
"POST /api/admin/endpoints/import",
|
||||
"POST /api/admin/endpoints/batch",
|
||||
"GET /api/admin/endpoints/{id}",
|
||||
"PATCH /api/admin/endpoints/{id}",
|
||||
"DELETE /api/admin/endpoints/{id}",
|
||||
"POST /api/admin/endpoints/{id}/kick",
|
||||
"POST /api/admin/endpoints/{id}/reset-login-password",
|
||||
"PUT /api/admin/endpoints/{id}/talk-password",
|
||||
"POST /api/admin/endpoints/{id}/unlock",
|
||||
"GET /api/admin/registration",
|
||||
"PUT /api/admin/registration",
|
||||
"GET /api/admin/groups",
|
||||
"POST /api/admin/groups",
|
||||
"GET /api/admin/groups/{id}",
|
||||
"PATCH /api/admin/groups/{id}",
|
||||
"DELETE /api/admin/groups/{id}",
|
||||
"POST /api/admin/groups/{id}/members",
|
||||
"DELETE /api/admin/groups/{id}/members/{endpointId}",
|
||||
"POST /api/admin/groups/{id}/transfer",
|
||||
"GET /api/admin/messages",
|
||||
"GET /api/admin/messages/{seq}",
|
||||
"GET /api/admin/settings",
|
||||
}
|
||||
Reference in New Issue
Block a user