diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/RateLimitsConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/RateLimitsConfiguration.java index 2aaded523..040e723ad 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/configuration/RateLimitsConfiguration.java +++ b/src/main/java/org/whispersystems/textsecuregcm/configuration/RateLimitsConfiguration.java @@ -29,6 +29,9 @@ public class RateLimitsConfiguration { @JsonProperty private RateLimitConfiguration voiceDestinationDaily = new RateLimitConfiguration(10, 10.0 / (24.0 * 60.0)); + @JsonProperty + private RateLimitConfiguration smsVoiceIp = new RateLimitConfiguration(1000, 1000); + @JsonProperty private RateLimitConfiguration verifyNumber = new RateLimitConfiguration(2, 2); @@ -95,6 +98,10 @@ public class RateLimitsConfiguration { return voiceDestinationDaily; } + public RateLimitConfiguration getSmsVoiceIp() { + return smsVoiceIp; + } + public RateLimitConfiguration getVerifyNumber() { return verifyNumber; } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 7b9d0e2e0..2aaf5247a 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -110,9 +110,10 @@ public class AccountController { @Timed @GET @Path("/{transport}/code/{number}") - public Response createAccount(@PathParam("transport") String transport, - @PathParam("number") String number, - @QueryParam("client") Optional client) + public Response createAccount(@PathParam("transport") String transport, + @PathParam("number") String number, + @HeaderParam("X-Forwarded-For") String requester, + @QueryParam("client") Optional client) throws IOException, RateLimitExceededException { if (!Util.isValidNumber(number)) { @@ -120,6 +121,13 @@ public class AccountController { throw new WebApplicationException(Response.status(400).build()); } + try { + rateLimiters.getSmsVoiceIpLimiter().validate(requester); + } catch (RateLimitExceededException e) { + logger.info("Rate limited exceeded: " + transport + ", " + number + ", " + requester); + return Response.ok().build(); + } + switch (transport) { case "sms": rateLimiters.getSmsDestinationLimiter().validate(number); diff --git a/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiters.java b/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiters.java index 4bbaabeee..e7999834d 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiters.java +++ b/src/main/java/org/whispersystems/textsecuregcm/limits/RateLimiters.java @@ -25,6 +25,7 @@ public class RateLimiters { private final RateLimiter smsDestinationLimiter; private final RateLimiter voiceDestinationLimiter; private final RateLimiter voiceDestinationDailyLimiter; + private final RateLimiter smsVoiceIpLimiter; private final RateLimiter verifyLimiter; private final RateLimiter pinLimiter; @@ -53,6 +54,10 @@ public class RateLimiters { config.getVoiceDestinationDaily().getBucketSize(), config.getVoiceDestinationDaily().getLeakRatePerMinute()); + this.smsVoiceIpLimiter = new RateLimiter(cacheClient, "smsVoiceIp", + config.getSmsVoiceIp().getBucketSize(), + config.getSmsVoiceIp().getLeakRatePerMinute()); + this.verifyLimiter = new RateLimiter(cacheClient, "verify", config.getVerifyNumber().getBucketSize(), config.getVerifyNumber().getLeakRatePerMinute()); @@ -122,6 +127,10 @@ public class RateLimiters { return smsDestinationLimiter; } + public RateLimiter getSmsVoiceIpLimiter() { + return smsVoiceIpLimiter; + } + public RateLimiter getVoiceDestinationLimiter() { return voiceDestinationLimiter; } diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index a3752f701..c8a23c085 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -49,6 +49,7 @@ public class AccountControllerTest { private RateLimiters rateLimiters = mock(RateLimiters.class ); private RateLimiter rateLimiter = mock(RateLimiter.class ); private RateLimiter pinLimiter = mock(RateLimiter.class ); + private RateLimiter smsVoiceIpLimiter = mock(RateLimiter.class ); private SmsSender smsSender = mock(SmsSender.class ); private DirectoryQueue directoryQueue = mock(DirectoryQueue.class); private MessagesManager storedMessages = mock(MessagesManager.class ); @@ -80,6 +81,7 @@ public class AccountControllerTest { when(rateLimiters.getVoiceDestinationLimiter()).thenReturn(rateLimiter); when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter); when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter); + when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter); when(timeProvider.getCurrentTimeMillis()).thenReturn(System.currentTimeMillis()); @@ -106,6 +108,7 @@ public class AccountControllerTest { resources.getJerseyTest() .target(String.format("/v1/accounts/sms/code/%s", SENDER)) .request() + .header("X-Forwarded-For", "127.0.0.1") .get(); assertThat(response.getStatus()).isEqualTo(200); @@ -120,6 +123,7 @@ public class AccountControllerTest { .target(String.format("/v1/accounts/sms/code/%s", SENDER)) .queryParam("client", "ios") .request() + .header("X-Forwarded-For", "127.0.0.1") .get(); assertThat(response.getStatus()).isEqualTo(200);