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

@@ -2,8 +2,11 @@ package httpserver
import (
"context"
"errors"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
"net/http"
"net/http/httptest"
"net/netip"
"strings"
"testing"
"time"
@@ -14,12 +17,13 @@ import (
)
type fakeConsoleAuth struct {
valid bool
login consoleauth.LoginResult
valid bool
login consoleauth.LoginResult
loginError error
}
func (f *fakeConsoleAuth) Login(context.Context, string, string, string, string) (consoleauth.LoginResult, error) {
return f.login, nil
return f.login, f.loginError
}
func (f *fakeConsoleAuth) ValidateSession(context.Context, auth.Token) error {
if !f.valid {
@@ -82,3 +86,69 @@ func TestConsoleLoginSetsRootScopedHttpOnlyCookie(t *testing.T) {
t.Fatalf("cookie path/httpOnly = %q/%v", cookie.Path, cookie.HttpOnly)
}
}
func TestConsoleLoginRejectsWithRetryAndNoCookie(t *testing.T) {
for _, tc := range []struct {
name string
err error
status int
code string
}{
{"password", fault.Unauthorized, 401, "UNAUTHENTICATED"},
{"locked", &consoleauth.LoginBlocked{Locked: true, RetryAfter: 1500 * time.Millisecond}, 429, "LOGIN_LOCKED"},
{"rate", &consoleauth.LoginBlocked{RetryAfter: 1500 * time.Millisecond}, 429, "LOGIN_RATE_LIMITED"},
{"storage", errors.New("private database error"), 503, "UNAVAILABLE"},
} {
t.Run(tc.name, func(t *testing.T) {
server := NewWithConsoleAuth("127.0.0.1:0", nil, &fakeConsoleAuth{loginError: tc.err})
req := httptest.NewRequest("POST", "/console/auth/login", strings.NewReader(`{"username":"admin","password":"password-password"}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
server.Handler.ServeHTTP(w, req)
if w.Code != tc.status || !strings.Contains(w.Body.String(), tc.code) || len(w.Result().Cookies()) != 0 || strings.Contains(w.Body.String(), "private database") {
t.Fatalf("response: %d %s", w.Code, w.Body.String())
}
if w.Code == 429 && w.Header().Get("Retry-After") != "2" {
t.Fatal("missing rounded Retry-After")
}
})
}
}
type policyConsoleAuth struct {
fakeConsoleAuth
policy consoleauth.LoginPolicy
}
func (s *policyConsoleAuth) LoginPolicy() consoleauth.LoginPolicy { return s.policy }
func TestLoginSourceOnlyTrustsConfiguredProxyChain(t *testing.T) {
s := &policyConsoleAuth{policy: consoleauth.DefaultLoginPolicy()}
s.policy.TrustedProxies = []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8"), netip.MustParsePrefix("::1/128")}
for _, tc := range []struct{ peer, forward, want string }{
{"192.0.2.1:1234", "198.51.100.7", "192.0.2.1"},
{"10.0.0.1:1234", "198.51.100.7, 10.0.0.2", "198.51.100.7"},
{"10.0.0.1:1234", "203.0.113.99, 198.51.100.7", "198.51.100.7"},
{"10.0.0.1:1234", "garbage, 10.0.0.2", "10.0.0.1"},
{"[::1]:1234", "::ffff:192.0.2.1", "192.0.2.1"},
} {
r := httptest.NewRequest("POST", "/console/auth/login", nil)
r.RemoteAddr = tc.peer
r.Header.Set("X-Forwarded-For", tc.forward)
r.Header.Set("X-Real-IP", "203.0.113.123")
if got := loginRemoteAddress(r, s); got != tc.want {
t.Fatalf("%s / %s: got %s", tc.peer, tc.forward, got)
}
}
}
func TestConsoleLoginRejectsCrossSite(t *testing.T) {
server := NewWithConsoleAuth("127.0.0.1:0", nil, &fakeConsoleAuth{})
r := httptest.NewRequest("POST", "/console/auth/login", strings.NewReader(`{"username":"admin","password":"wrong"}`))
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Origin", "https://evil.invalid")
w := httptest.NewRecorder()
server.Handler.ServeHTTP(w, r)
if w.Code != 403 || len(w.Result().Cookies()) != 0 {
t.Fatalf("cross-site response: %d", w.Code)
}
}