from collections.abc import Mapping OTP_SETTING_KEYS = { "otp.phone.max_send_attempts_per_24h", "otp.phone.min_seconds_between_attempts", "otp.phone.max_verify_attempts", "otp.phone.code_length", "otp.phone.ttl_seconds", "otp.phone.sms_order_timeout_ms", } def validate_otp_settings(values: Mapping[str, str]) -> None: parsed: dict[str, int] = {} for key in OTP_SETTING_KEYS: raw = values.get(key) if raw is None: continue try: value = int(raw) except (TypeError, ValueError) as error: raise ValueError(f"{key}: integer value expected") from error if str(value) != raw: raise ValueError(f"{key}: canonical integer value expected") parsed[key] = value positive = OTP_SETTING_KEYS - {"otp.phone.min_seconds_between_attempts"} for key in positive: if key in parsed and parsed[key] <= 0: raise ValueError(f"{key}: value must be positive") if parsed.get("otp.phone.min_seconds_between_attempts", 0) < 0: raise ValueError("otp.phone.min_seconds_between_attempts: value must be non-negative") code_length = parsed.get("otp.phone.code_length") if code_length is not None and not 4 <= code_length <= 10: raise ValueError("otp.phone.code_length: value must be between 4 and 10") ttl_seconds = parsed.get("otp.phone.ttl_seconds") if ttl_seconds is not None and ( not 60 <= ttl_seconds <= 900 or ttl_seconds % 60 != 0 ): raise ValueError( "otp.phone.ttl_seconds: value must be between 60 and 900 and divisible by 60" )