1
0
mirror of synced 2025-04-19 11:02:15 +03:00

Add some better ratelimiting to email sending (#526)

This commit is contained in:
Erik Johnston 2022-11-18 12:51:13 +00:00 committed by GitHub
parent f86eb4126e
commit 37bd539ed7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 35 additions and 2 deletions

1
changelog.d/526.misc Normal file
View File

@ -0,0 +1 @@
Add ratelimiting to email sending.

View File

@ -45,6 +45,8 @@ email.smtphost = localhost
email.from = Sydent Validation <noreply@localhost>
email.smtpport = 9925
email.subject = Your Validation Token
email.ratelimit_sender.burst = 100000
email.ratelimit_sender.rate_hz = 100000
"""

View File

@ -70,4 +70,11 @@ class EmailConfig(BaseConfig):
"email", "email.third_party_invite_domain_obfuscate_characters"
)
self.email_sender_ratelimit_burst = cfg.getint(
"email", "email.ratelimit_sender.burst", fallback=5
)
self.email_sender_ratelimit_rate_hz = cfg.getfloat(
"email", "email.ratelimit_sender.rate_hz", fallback=1.0 / (5 * 60.0)
)
return False

View File

@ -44,8 +44,16 @@ class EmailRequestCodeServlet(Resource):
def render_POST(self, request: Request) -> JsonDict:
send_cors(request)
ipaddress = self.sydent.ip_from_request(request)
if self.require_auth:
authV2(self.sydent, request)
account = authV2(self.sydent, request)
self.sydent.email_sender_ratelimiter.ratelimit(account.userId)
elif ipaddress:
# For `/v1/` requests the ip address is the best we can do for rate
# limiting.
self.sydent.email_sender_ratelimiter.ratelimit(ipaddress)
args = get_args(request, ("email", "client_secret", "send_attempt"))
@ -84,7 +92,6 @@ class EmailRequestCodeServlet(Resource):
request.setResponseCode(400)
return {"errcode": "M_INVALID_PARAM", "error": "Invalid email provided"}
ipaddress = self.sydent.ip_from_request(request)
brand = self.sydent.brand_from_request(request)
nextLink: Optional[str] = None

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import random
import string
from email.header import Header
@ -31,6 +32,8 @@ from sydent.types import JsonDict
from sydent.util.emailutils import EmailAddressException, sendEmail
from sydent.util.stringutils import MAX_EMAIL_ADDRESS_LENGTH, normalise_address
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from sydent.sydent import Sydent
@ -69,6 +72,12 @@ class StoreInviteServlet(Resource):
if account.userId != sender:
raise MatrixRestError(403, "M_UNAUTHORIZED", "'sender' doesn't match")
self.sydent.email_sender_ratelimiter.ratelimit(sender)
logger.info(
"Store invite request from %s to %s, in %s", sender, address, roomId
)
globalAssocStore = GlobalAssociationStore(self.sydent)
mxid = globalAssocStore.getMxid(medium, normalised_address)
if mxid:

View File

@ -81,6 +81,7 @@ from sydent.http.servlets.v2_servlet import V2Servlet
from sydent.replication.pusher import Pusher
from sydent.threepid.bind import ThreepidBinder
from sydent.util.hash import sha256_and_url_safe_base64
from sydent.util.ratelimiter import Ratelimiter
from sydent.util.tokenutils import generateAlphanumericTokenOfLength
from sydent.validators.emailvalidator import EmailValidator
from sydent.validators.msisdnvalidator import MsisdnValidator
@ -169,6 +170,12 @@ class Sydent:
self.pusher: Pusher = Pusher(self)
self.email_sender_ratelimiter: Ratelimiter[str] = Ratelimiter(
self.reactor,
burst=self.config.email.email_sender_ratelimit_burst,
rate_hz=self.config.email.email_sender_ratelimit_rate_hz,
)
def run(self) -> None:
self.clientApiHttpServer.setup()
self.replicationHttpsServer.setup()