@@ -233,6 +233,54 @@ fn parse_bind_addr(raw: &str) -> Result<SocketAddr, ConfigError> {
233233 . map_err ( |e| ConfigError :: InvalidBindAddr ( e. to_string ( ) ) )
234234}
235235
236+ fn positive_u64_from_env ( name : & str , default : u64 ) -> Result < u64 , ConfigError > {
237+ match std:: env:: var ( name) {
238+ Ok ( raw) => raw
239+ . parse :: < u64 > ( )
240+ . ok ( )
241+ . filter ( |value| * value > 0 )
242+ . ok_or_else ( || ConfigError :: InvalidValue ( format ! ( "{name} must be a positive integer" ) ) ) ,
243+ Err ( std:: env:: VarError :: NotPresent ) => Ok ( default) ,
244+ Err ( std:: env:: VarError :: NotUnicode ( _) ) => Err ( ConfigError :: InvalidValue ( format ! (
245+ "{name} must be valid Unicode"
246+ ) ) ) ,
247+ }
248+ }
249+
250+ fn rate_limit_config_from_env ( ) -> Result < buzz_auth:: RateLimitConfig , ConfigError > {
251+ let defaults = buzz_auth:: RateLimitConfig :: default ( ) ;
252+ Ok ( buzz_auth:: RateLimitConfig {
253+ human_messages_per_min : positive_u64_from_env (
254+ "BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN" ,
255+ defaults. human_messages_per_min ,
256+ ) ?,
257+ human_api_calls_per_min : positive_u64_from_env (
258+ "BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN" ,
259+ defaults. human_api_calls_per_min ,
260+ ) ?,
261+ human_ws_events_per_sec : positive_u64_from_env (
262+ "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" ,
263+ defaults. human_ws_events_per_sec ,
264+ ) ?,
265+ agent_standard_messages_per_min : positive_u64_from_env (
266+ "BUZZ_RATE_LIMIT_AGENT_STANDARD_MESSAGES_PER_MIN" ,
267+ defaults. agent_standard_messages_per_min ,
268+ ) ?,
269+ agent_standard_api_calls_per_min : positive_u64_from_env (
270+ "BUZZ_RATE_LIMIT_AGENT_STANDARD_API_CALLS_PER_MIN" ,
271+ defaults. agent_standard_api_calls_per_min ,
272+ ) ?,
273+ agent_elevated_messages_per_min : positive_u64_from_env (
274+ "BUZZ_RATE_LIMIT_AGENT_ELEVATED_MESSAGES_PER_MIN" ,
275+ defaults. agent_elevated_messages_per_min ,
276+ ) ?,
277+ agent_platform_messages_per_min : positive_u64_from_env (
278+ "BUZZ_RATE_LIMIT_AGENT_PLATFORM_MESSAGES_PER_MIN" ,
279+ defaults. agent_platform_messages_per_min ,
280+ ) ?,
281+ } )
282+ }
283+
236284fn parse_operator_api_origin ( raw : & str ) -> Result < String , ConfigError > {
237285 let raw = raw. trim ( ) ;
238286 let url = url:: Url :: parse ( raw) . map_err ( |e| {
@@ -477,7 +525,9 @@ impl Config {
477525 ) ) ;
478526 }
479527
480- let auth = buzz_auth:: AuthConfig :: default ( ) ;
528+ let auth = buzz_auth:: AuthConfig {
529+ rate_limits : rate_limit_config_from_env ( ) ?,
530+ } ;
481531
482532 if !require_auth_token {
483533 warn ! (
@@ -842,6 +892,37 @@ mod tests {
842892 ) ) ;
843893 }
844894
895+ #[ test]
896+ fn rate_limits_can_be_overridden ( ) {
897+ let _guard = ENV_MUTEX . lock ( ) . unwrap ( ) ;
898+ std:: env:: set_var ( "BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN" , "1001" ) ;
899+ std:: env:: set_var ( "BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN" , "1002" ) ;
900+ std:: env:: set_var ( "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" , "1003" ) ;
901+
902+ let config = Config :: from_env ( ) . expect ( "config" ) ;
903+
904+ std:: env:: remove_var ( "BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN" ) ;
905+ std:: env:: remove_var ( "BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN" ) ;
906+ std:: env:: remove_var ( "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" ) ;
907+ assert_eq ! ( config. auth. rate_limits. human_messages_per_min, 1001 ) ;
908+ assert_eq ! ( config. auth. rate_limits. human_api_calls_per_min, 1002 ) ;
909+ assert_eq ! ( config. auth. rate_limits. human_ws_events_per_sec, 1003 ) ;
910+ }
911+
912+ #[ test]
913+ fn rate_limit_overrides_reject_zero ( ) {
914+ let _guard = ENV_MUTEX . lock ( ) . unwrap ( ) ;
915+ std:: env:: set_var ( "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" , "0" ) ;
916+ let result = Config :: from_env ( ) ;
917+ std:: env:: remove_var ( "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" ) ;
918+
919+ assert ! ( matches!(
920+ result,
921+ Err ( ConfigError :: InvalidValue ( ref message) )
922+ if message. contains( "BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC" )
923+ ) ) ;
924+ }
925+
845926 #[ test]
846927 fn relay_operator_pubkeys_parse_dedupe_and_normalize ( ) {
847928 let _guard = ENV_MUTEX . lock ( ) . unwrap ( ) ;
0 commit comments