fix: stabilize console scrolling and protect administrator login
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user