* docs(autoscan): add arr webhook intake spec and implementation plan Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): add webhook intake schema migration Adds delivery_mode to autoscan_sources, the autoscan_webhook_endpoints table, and delivery_mode/provider_event_type on autoscan_events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): add built-in arr-webhook source identity Host-discovered scan-source entry so webhook-mode sources need no plugin installation; composite lister appends it to plugin discovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): persist delivery mode, webhook endpoints, event metadata Sources carry delivery_mode; autoscan_webhook_endpoints CRUD with SHA-256 token lookup and AAD-bound encrypted redisplay; events record delivery_mode/provider_event_type; CreateEvent gains SkipRunningCheck so webhook deliveries are never dropped by the poll exclusion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): share the consume path and add webhook IngestChanges Extracts consumeSourceChanges from PollOnce (marker semantics preserved, existing poll tests unchanged); PollOnce skips webhook sources; IngestChanges feeds deliveries through the shared pipeline without markers and without the running-event exclusion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): add Sonarr/Radarr webhook payload parser Host-side arrwebhook package: provider inference, import/rename/delete path extraction with vanished-path-friendly previous paths, subtree fallback, exact-path dedupe, and no-op unknown events. Fixture-backed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(autoscan): add public webhook delivery route and admin endpoint management Public POST /api/v1/autoscan/webhooks/{token} with per-IP rate limiting, 256KiB body cap, 202-for-noop semantics, and token/body kept out of logs; admin create/rotate/delete endpoint routes; source responses carry delivery mode + webhook status/URL; create/update validate delivery mode against source identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): add webhook delivery mode to Autoscan admin UI Webhook sources get a generate/copy/rotate webhook URL section, provider selector, delivery status, and a connection-free Add-source flow; activity rows badge webhook deliveries with the arr event type. Path rewrites stay editable in both modes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(api): redact secret path params from request and activity logs The request logger and activity-log middleware recorded raw URLs, so bearer credentials in secret path segments (autoscan webhook {token}, webhook-sync {secret}) were persisted to app logs and activity_log. Redact the secret segment via the chi route params in both sinks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(autoscan): make webhook delivery reliable --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.3 KiB
Go
81 lines
2.3 KiB
Go
package ratelimit
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// Rate defines the rate limits for a key.
|
|
type Rate struct {
|
|
RequestsPerSecond float64
|
|
RequestsPerMinute float64
|
|
Burst int // immediate burst allowance (token bucket or Redis second window)
|
|
}
|
|
|
|
// AllowResult contains the result of a rate limit check.
|
|
type AllowResult struct {
|
|
Allowed bool
|
|
RetryAfter time.Duration
|
|
Limit int
|
|
Remaining int
|
|
ResetAt time.Time
|
|
}
|
|
|
|
// RateLimiter checks whether a request is allowed under the given rate.
|
|
type RateLimiter interface {
|
|
Allow(ctx context.Context, key string, limit Rate) AllowResult
|
|
Close()
|
|
}
|
|
|
|
// TierConfig holds the rate configuration for a tier.
|
|
type TierConfig struct {
|
|
RequestsPerSecond float64
|
|
RequestsPerMinute float64
|
|
Burst int
|
|
}
|
|
|
|
// AuthEndpointConfig holds per-endpoint rate limit settings for auth endpoints.
|
|
type AuthEndpointConfig struct {
|
|
RequestsPerMinute float64
|
|
Burst int
|
|
}
|
|
|
|
// Config holds all runtime rate limit settings loaded from server_settings.
|
|
type Config struct {
|
|
Enabled bool
|
|
GlobalReqPerSecond float64
|
|
Tiers map[string]TierConfig
|
|
// IP-based rate limiting
|
|
IPReqPerSecond float64
|
|
IPReqPerMinute float64
|
|
IPBurst int
|
|
// Auth endpoint per-IP limits
|
|
AuthEndpoints map[string]AuthEndpointConfig
|
|
}
|
|
|
|
// DefaultConfig returns the default rate limit configuration.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
Enabled: true,
|
|
GlobalReqPerSecond: 1000,
|
|
Tiers: map[string]TierConfig{
|
|
"standard": {RequestsPerSecond: 20, RequestsPerMinute: 1200, Burst: 20},
|
|
"elevated": {RequestsPerSecond: 100, RequestsPerMinute: 6000, Burst: 100},
|
|
},
|
|
IPReqPerSecond: 120,
|
|
IPReqPerMinute: 6000,
|
|
IPBurst: 120,
|
|
AuthEndpoints: map[string]AuthEndpointConfig{
|
|
"login": {RequestsPerMinute: 20, Burst: 10},
|
|
"signup": {RequestsPerMinute: 10, Burst: 6},
|
|
"setup": {RequestsPerMinute: 10, Burst: 6},
|
|
"device_start": {RequestsPerMinute: 20, Burst: 10},
|
|
"device_lookup": {RequestsPerMinute: 60, Burst: 20},
|
|
"device_poll": {RequestsPerMinute: 120, Burst: 30},
|
|
// Public autoscan webhook intake. Generous: arr fires one delivery
|
|
// per imported file, so season packs are legitimate bursts.
|
|
"autoscan_webhook": {RequestsPerMinute: 60, Burst: 30},
|
|
},
|
|
}
|
|
}
|