diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 8de99a1bb..b7ca28184 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -1129,7 +1129,7 @@ public class WhisperServerService extends Application existingAccount = accounts.getByE164(number); @@ -169,6 +177,14 @@ public class RegistrationController { registrationRequest.deviceActivationRequest().pniPqLastResortPreKey()), userAgent); + if (verificationType == PhoneVerificationRequest.VerificationType.SESSION) { + try { + registrationFraudChecker.handleVerificationCompleted(registrationRequest.sessionId(), account); + } catch (final Exception e) { + logger.warn("Failed to notify fraud checker of completed verification session", e); + } + } + Metrics.counter(ACCOUNT_CREATED_COUNTER_NAME, Tags.of(UserAgentTagUtil.getPlatformTag(userAgent), Tag.of(COUNTRY_CODE_TAG_NAME, Util.getCountryCode(number)), Tag.of(REGION_CODE_TAG_NAME, Util.getRegion(number)), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/spam/RegistrationFraudChecker.java b/service/src/main/java/org/whispersystems/textsecuregcm/spam/RegistrationFraudChecker.java index d30c93527..6a6034390 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/spam/RegistrationFraudChecker.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/spam/RegistrationFraudChecker.java @@ -8,6 +8,7 @@ import jakarta.ws.rs.container.ContainerRequestContext; import java.util.Optional; import org.whispersystems.textsecuregcm.entities.UpdateVerificationSessionRequest; import org.whispersystems.textsecuregcm.registration.VerificationSession; +import org.whispersystems.textsecuregcm.storage.Account; public interface RegistrationFraudChecker { @@ -47,6 +48,14 @@ public interface RegistrationFraudChecker { final VerificationSession verificationSession, final String e164); + /** + * Handles a completed verification session resulting in a registration. + * + * @param verificationSessionId The ID of the verification session that resulted in a completed registration + * @param createdAccount The account created at the end of the verification session + */ + void handleVerificationCompleted(final String verificationSessionId, final Account createdAccount); + static RegistrationFraudChecker noop() { return new RegistrationFraudChecker() { @@ -64,6 +73,10 @@ public interface RegistrationFraudChecker { return VerificationCheck.DEFAULT; } + + @Override + public void handleVerificationCompleted(final String verificationSessionId, final Account createdAccount) { + } }; } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java index 4cd9b7135..9203b54d3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java @@ -11,6 +11,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -75,6 +76,7 @@ import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMa import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; +import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker; import org.whispersystems.textsecuregcm.spam.RegistrationRecoveryChecker; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -109,6 +111,7 @@ class RegistrationControllerTest { RegistrationRecoveryPasswordsManager.class); private final RegistrationRecoveryChecker registrationRecoveryChecker = mock(RegistrationRecoveryChecker.class); private final RateLimiters rateLimiters = mock(RateLimiters.class); + private final RegistrationFraudChecker registrationFraudChecker = mock(RegistrationFraudChecker.class); private final RateLimiter registrationLimiter = mock(RateLimiter.class); @@ -123,7 +126,7 @@ class RegistrationControllerTest { new RegistrationController(accountsManager, new PhoneVerificationTokenManager(phoneNumberIdentifiers, registrationServiceClient, registrationRecoveryPasswordsManager, registrationRecoveryChecker), - registrationLockVerificationManager, rateLimiters)) + registrationLockVerificationManager, rateLimiters, registrationFraudChecker)) .build(); @BeforeEach @@ -138,6 +141,8 @@ class RegistrationControllerTest { return invocation.getArgument(0); }); + + reset(registrationFraudChecker); } @Test @@ -497,9 +502,14 @@ class RegistrationControllerTest { .target("/v1/registration") .request() .header(HttpHeaders.AUTHORIZATION, AuthHelper.getProvisioningAuthHeader(NUMBER, PASSWORD)); + try (Response response = request.post(Entity.json(requestJson("sessionId")))) { assertEquals(200, response.getStatus()); } + + verify(registrationFraudChecker) + .handleVerificationCompleted(Base64.getEncoder().encodeToString("sessionId".getBytes(StandardCharsets.UTF_8)), + account); } @ParameterizedTest