fix: stabilize console scrolling and protect administrator login

This commit is contained in:
2026-09-30 00:09:01 +08:00
parent 9a15406f76
commit f56f233704
32 changed files with 1308 additions and 86 deletions

View File

@@ -4,11 +4,14 @@ import (
"context"
"embed"
"encoding/json"
"errors"
"io/fs"
"math"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -64,6 +67,12 @@ func RegisterConsole(server *http.Server, authStores ...consoleauth.Store) {
writeConsoleJSON(w, http.StatusOK, map[string]any{"authenticated": true})
})
mux.HandleFunc("POST /console/auth/login", func(w http.ResponseWriter, r *http.Request) {
if err := http.NewCrossOriginProtection().Check(r); err != nil {
writeConsoleJSON(w, http.StatusForbidden, map[string]any{"error": map[string]string{"code": "PERMISSION_DENIED", "message": "不允许跨站登录请求"}})
return
}
r.Body = http.MaxBytesReader(w, r.Body, 8<<10)
if consoleAuth == nil {
writeConsoleJSON(w, http.StatusServiceUnavailable, map[string]any{"error": map[string]string{"code": "UNAVAILABLE", "message": "console authentication is not configured"}})
return
@@ -78,10 +87,23 @@ func RegisterConsole(server *http.Server, authStores ...consoleauth.Store) {
}
ctx, cancel := context.WithTimeout(r.Context(), 8*time.Second)
defer cancel()
login, err := consoleAuth.Login(ctx, in.Username, in.Password, r.UserAgent(), remoteAddress(r))
login, err := consoleAuth.Login(ctx, in.Username, in.Password, r.UserAgent(), loginRemoteAddress(r, consoleAuth))
if err != nil {
// 账号不存在、密码错误、账号停用和绑定凭据失效统一返回,避免账号枚举。
writeConsoleJSON(w, http.StatusUnauthorized, map[string]any{"error": map[string]string{"code": "UNAUTHENTICATED", "message": "用户名或密码错误"}})
var blocked *consoleauth.LoginBlocked
if errors.As(err, &blocked) {
seconds := max(1, int(math.Ceil(blocked.RetryAfter.Seconds())))
code, message := "LOGIN_RATE_LIMITED", "登录尝试过于频繁,请稍后重试"
if blocked.Locked {
code, message = "LOGIN_LOCKED", "连续登录失败,已临时锁定,请稍后重试"
}
w.Header().Set("Retry-After", strconv.Itoa(seconds))
writeConsoleJSON(w, http.StatusTooManyRequests, map[string]any{"error": map[string]any{"code": code, "message": message, "retry_after_seconds": seconds}})
} else if errors.Is(err, fault.Unauthorized) {
writeConsoleJSON(w, http.StatusUnauthorized, map[string]any{"error": map[string]string{"code": "UNAUTHENTICATED", "message": "用户名或密码错误"}})
} else {
// 存储或审计不可用时不发 Cookie,不将基础设施故障伪装成密码错误。
writeConsoleJSON(w, http.StatusServiceUnavailable, map[string]any{"error": map[string]string{"code": "UNAVAILABLE", "message": "登录服务暂不可用,请稍后重试"}})
}
return
}
http.SetCookie(w, &http.Cookie{Name: consoleSessionCookie, Value: login.Token, Path: "/", HttpOnly: true, Secure: r.TLS != nil, SameSite: http.SameSiteLaxMode, Expires: login.ExpiresAt, MaxAge: int(time.Until(login.ExpiresAt).Seconds())})