Address code review findings across API, web and deployment
release gate / check (push) Failing after 6m43s
release gate / check (push) Failing after 6m43s
High severity: - wire TRUSTED_PROXY through Compose/deploy/docs and warn at web startup - bucket public rate limits on effective client IP, add per-bookmark budget, and forward the signed browser identity from the share page - disable first-run bootstrap in server deployments and document seeding - serialize last-admin demotion in one advisory-locked transaction Medium/low: - cap and batch share recipient inserts - add a shared outbound budget for preview/CardDAV/IMAP/signal work - pin CardDAV dials to 443 and bound IMAP logout - fix rightward kanban column drags and normalize cards on column delete - classify backend outages in auth guards, add request timeouts and an error boundary, remove the dead login route, surface throttling - enforce pinned/unpinned page reorder grouping server-side - avoid preview work for stale bookmark versions; drop duplicate dialog fetch - sequence-guard collection/tag/calendar/quick-find loads - validate property config and fix MULTI_SELECT/DATE filter inputs - document index maintenance; apply same-origin check to sign-out - trim signup names, unique shared dates and kanban keys (migration 010), reset reference cache on identity changes, hide OIDC issuer, return 413 for oversized direct bodies, escape Markdown link destinations - remove dead template files and duplicate test helper Verified with go build/vet, full PostgreSQL race suite, db convergence, tsc, both ESLint configs and a Next production build.
This commit is contained in:
@@ -93,8 +93,8 @@ func TestFailureTrackerEviction(t *testing.T) {
|
||||
func dummyHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
|
||||
|
||||
func TestRateLimitMiddleware(t *testing.T) {
|
||||
resetRateLimits()
|
||||
t.Cleanup(resetRateLimits)
|
||||
ResetRateLimits()
|
||||
t.Cleanup(ResetRateLimits)
|
||||
|
||||
// Review SEC-1: loopback is NOT exempt on the login endpoint — same-host
|
||||
// deployments must keep throttling for real browser logins.
|
||||
@@ -109,7 +109,7 @@ func TestRateLimitMiddleware(t *testing.T) {
|
||||
t.Fatalf("loopback login exempt: got %d", loopLast.Code)
|
||||
}
|
||||
// Signup keeps the loopback exemption for host-local consumers.
|
||||
resetRateLimits()
|
||||
ResetRateLimits()
|
||||
var loopSignup *httptest.ResponseRecorder
|
||||
for i := 0; i < signupIPLimit*2; i++ {
|
||||
loopSignup = httptest.NewRecorder()
|
||||
@@ -118,7 +118,7 @@ func TestRateLimitMiddleware(t *testing.T) {
|
||||
if loopSignup.Code != 204 {
|
||||
t.Fatalf("loopback signup limited with %d", loopSignup.Code)
|
||||
}
|
||||
resetRateLimits()
|
||||
ResetRateLimits()
|
||||
|
||||
// Non-loopback peers get 429 after the window limit, with Retry-After.
|
||||
remote := httptest.NewRequest("POST", "/api/auth/login", nil)
|
||||
@@ -136,7 +136,7 @@ func TestRateLimitMiddleware(t *testing.T) {
|
||||
}
|
||||
|
||||
// Signup limiter counts independently.
|
||||
resetRateLimits()
|
||||
ResetRateLimits()
|
||||
var signupLast *httptest.ResponseRecorder
|
||||
for i := 0; i < signupIPLimit+1; i++ {
|
||||
signupLast = httptest.NewRecorder()
|
||||
@@ -207,10 +207,48 @@ func TestSignedForwardedClientIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Review H-2: public endpoints must bucket on the signed forwarded browser
|
||||
// identity too, not the shared Next container peer.
|
||||
func TestPublicLimiterUsesForwardedIP(t *testing.T) {
|
||||
t.Setenv("NEXTAUTH_SECRET", "")
|
||||
t.Setenv("AUTH_SECRET", "public-limiter-forward-secret")
|
||||
ts := time.Now().Unix()
|
||||
sign := func(ip string) (string, string) {
|
||||
mac := hmac.New(sha256.New, []byte("public-limiter-forward-secret"))
|
||||
mac.Write([]byte(ip + "|" + strconv.FormatInt(ts, 10)))
|
||||
return strconv.FormatInt(ts, 10), hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
handler := Limiter(NewLimiterStore(), 2, time.Minute)(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
do := func(ip string) int {
|
||||
r := httptest.NewRequest("GET", "/api/public/bookmarks/x", nil)
|
||||
r.RemoteAddr = "10.0.0.5:1234"
|
||||
hts, sig := sign(ip)
|
||||
r.Header.Set(headerClientIP, ip)
|
||||
r.Header.Set(headerClientTs, hts)
|
||||
r.Header.Set(headerClientSig, sig)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, r)
|
||||
return w.Code
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
if code := do("198.51.100.1"); code != http.StatusOK {
|
||||
t.Fatalf("browser 1 request %d: %d", i+1, code)
|
||||
}
|
||||
}
|
||||
if code := do("198.51.100.1"); code != http.StatusTooManyRequests {
|
||||
t.Fatalf("browser 1 over limit: %d", code)
|
||||
}
|
||||
if code := do("198.51.100.2"); code != http.StatusOK {
|
||||
t.Fatalf("browser 2 blocked by browser 1: %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardedIPsGetSeparateBuckets(t *testing.T) {
|
||||
t.Setenv("NEXTAUTH_SECRET", "")
|
||||
t.Setenv("AUTH_SECRET", "ratelimit-forward-secret")
|
||||
resetRateLimits()
|
||||
ResetRateLimits()
|
||||
ts := time.Now().Unix()
|
||||
sign := func(ip string) (string, string) {
|
||||
mac := hmac.New(sha256.New, []byte("ratelimit-forward-secret"))
|
||||
@@ -241,5 +279,5 @@ func TestForwardedIPsGetSeparateBuckets(t *testing.T) {
|
||||
if code := do("198.51.100.2"); code != 200 {
|
||||
t.Fatalf("browser 2 blocked by browser 1: %d", code)
|
||||
}
|
||||
resetRateLimits()
|
||||
ResetRateLimits()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user