@@ -38,6 +38,26 @@ type Config struct { |
| 38 | 38 | Storage StorageConfig `toml:"storage"` |
| 39 | 39 | Auth AuthConfig `toml:"auth"` |
| 40 | 40 | Notif NotifConfig `toml:"notif"` |
| 41 | + RateLimit RateLimitConfig `toml:"ratelimit"` |
| 42 | +} |
| 43 | + |
| 44 | +// RateLimitConfig configures runtime rate-limit budgets for surfaces that |
| 45 | +// don't carry a domain-specific limiter. The /api/v1/ JSON surface uses |
| 46 | +// the API.* sub-block; future surfaces (search, git transports) get their |
| 47 | +// own sub-blocks here as they're factored out of their handlers. |
| 48 | +type RateLimitConfig struct { |
| 49 | + API APIRateLimitConfig `toml:"api"` |
| 50 | +} |
| 51 | + |
| 52 | +// APIRateLimitConfig sets the per-hour budgets for /api/v1/* requests. |
| 53 | +// AuthedPerHour applies when the caller presents a valid PAT (keyed by |
| 54 | +// token id). AnonPerHour applies to unauthenticated callers (keyed by |
| 55 | +// remote IP). Defaults are GitHub-aligned: 5000/h authed, 60/h anon — |
| 56 | +// operators tune via SHITHUB_RATELIMIT__API__AUTHED_PER_HOUR / |
| 57 | +// SHITHUB_RATELIMIT__API__ANON_PER_HOUR. |
| 58 | +type APIRateLimitConfig struct { |
| 59 | + AuthedPerHour int `toml:"authed_per_hour"` |
| 60 | + AnonPerHour int `toml:"anon_per_hour"` |
| 41 | 61 | } |
| 42 | 62 | |
| 43 | 63 | // NotifConfig configures the S29 notification surface. UnsubscribeKeyB64 |
@@ -205,6 +225,12 @@ func Defaults() Config { |
| 205 | 225 | ForcePathStyle: true, |
| 206 | 226 | }, |
| 207 | 227 | }, |
| 228 | + RateLimit: RateLimitConfig{ |
| 229 | + API: APIRateLimitConfig{ |
| 230 | + AuthedPerHour: 5000, |
| 231 | + AnonPerHour: 60, |
| 232 | + }, |
| 233 | + }, |
| 208 | 234 | Auth: AuthConfig{ |
| 209 | 235 | RequireEmailVerification: true, |
| 210 | 236 | BaseURL: "http://127.0.0.1:8080", |
@@ -321,6 +347,18 @@ func Validate(c *Config) error { |
| 321 | 347 | if c.Auth.EmailFrom == "" { |
| 322 | 348 | return errors.New("config: auth.email_from is required") |
| 323 | 349 | } |
| 350 | + if c.RateLimit.API.AuthedPerHour < 0 { |
| 351 | + return fmt.Errorf("config: ratelimit.api.authed_per_hour: must be >= 0, got %d", c.RateLimit.API.AuthedPerHour) |
| 352 | + } |
| 353 | + if c.RateLimit.API.AnonPerHour < 0 { |
| 354 | + return fmt.Errorf("config: ratelimit.api.anon_per_hour: must be >= 0, got %d", c.RateLimit.API.AnonPerHour) |
| 355 | + } |
| 356 | + if c.RateLimit.API.AuthedPerHour == 0 { |
| 357 | + c.RateLimit.API.AuthedPerHour = 5000 |
| 358 | + } |
| 359 | + if c.RateLimit.API.AnonPerHour == 0 { |
| 360 | + c.RateLimit.API.AnonPerHour = 60 |
| 361 | + } |
| 324 | 362 | return nil |
| 325 | 363 | } |
| 326 | 364 | |