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:
@@ -0,0 +1,75 @@
|
||||
// Package outbound bounds server-initiated network work — URL previews,
|
||||
// CardDAV syncs, IMAP test/check passes and manual signal checks — so an
|
||||
// authenticated user cannot open unbounded outbound connections, exhaust
|
||||
// file descriptors or saturate egress (review M-2).
|
||||
//
|
||||
// Two layers are shared by every caller:
|
||||
// - a global in-flight cap (defaultConcurrent), and
|
||||
// - a per-identity fixed-window rate (the caller supplies the limit/window,
|
||||
// since a preview is cheaper than an IMAP search).
|
||||
//
|
||||
// Exhaustion is immediate: previews degrade to no metadata and interactive
|
||||
// endpoints answer 429 with Retry-After. Nothing queues behind the budget.
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultConcurrent = 8
|
||||
|
||||
type window struct {
|
||||
count int
|
||||
reset time.Time
|
||||
}
|
||||
|
||||
type budget struct {
|
||||
mu sync.Mutex
|
||||
inFlight int
|
||||
max int
|
||||
keys map[string]*window
|
||||
}
|
||||
|
||||
var shared = &budget{max: defaultConcurrent, keys: map[string]*window{}}
|
||||
|
||||
// Reserve claims one outbound slot for key. The returned release MUST be
|
||||
// called when the work finishes. When ok is false, retry is a conservative
|
||||
// Retry-After duration.
|
||||
func Reserve(key string, limit int, windowDur time.Duration) (release func(), retry time.Duration, ok bool) {
|
||||
if key == "" {
|
||||
key = "anonymous"
|
||||
}
|
||||
shared.mu.Lock()
|
||||
defer shared.mu.Unlock()
|
||||
if shared.inFlight >= shared.max {
|
||||
return nil, time.Second, false
|
||||
}
|
||||
now := time.Now()
|
||||
if len(shared.keys) > 4096 {
|
||||
for k, w := range shared.keys {
|
||||
if now.After(w.reset) {
|
||||
delete(shared.keys, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
w := shared.keys[key]
|
||||
if w == nil || now.After(w.reset) {
|
||||
w = &window{reset: now.Add(windowDur)}
|
||||
shared.keys[key] = w
|
||||
}
|
||||
if limit > 0 && w.count >= limit {
|
||||
left := w.reset.Sub(now)
|
||||
if left < 0 {
|
||||
left = 0
|
||||
}
|
||||
return nil, left.Truncate(time.Second) + time.Second, false
|
||||
}
|
||||
w.count++
|
||||
shared.inFlight++
|
||||
return func() {
|
||||
shared.mu.Lock()
|
||||
shared.inFlight--
|
||||
shared.mu.Unlock()
|
||||
}, 0, true
|
||||
}
|
||||
Reference in New Issue
Block a user