Only send verification code push notifications to primary devices

This commit is contained in:
Jon Chambers
2026-05-22 16:29:48 -04:00
committed by Jon Chambers
parent 9f6d80cb39
commit cb6123125f
3 changed files with 25 additions and 23 deletions
@@ -82,6 +82,7 @@ import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExcepti
import org.whispersystems.textsecuregcm.metrics.CaptchaMetrics;
import org.whispersystems.textsecuregcm.metrics.DevicePlatformUtil;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotification;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.registration.ClientType;
@@ -655,8 +656,12 @@ public class VerificationController {
accountsManager.getByE164(registrationServiceSession.number())
.filter(existingAccount ->
experimentEnrollmentManager.isEnrolled(existingAccount.getIdentifier(IdentityType.ACI), VERIFICATION_CODE_PUSH_NOTIFICATION_EXPERIMENT_NAME))
.ifPresent(existingAccount ->
pushNotificationManager.trySendVerificationCodeRequestedNotifications(existingAccount, clock.instant()));
.ifPresent(existingAccount -> {
try {
pushNotificationManager.sendVerificationCodeRequestedNotifications(existingAccount, clock.instant());
} catch (final NotPushRegisteredException _) {
}
});
} catch (final CancellationException e) {
throw new ServerErrorException("registration service unavailable", Response.Status.SERVICE_UNAVAILABLE);
} catch (final CompletionException e) {
@@ -87,26 +87,21 @@ public class PushNotificationManager {
.thenApply(maybeResponse -> maybeResponse.orElseThrow(() -> new AssertionError("Responses must be present for urgent notifications")));
}
public CompletableFuture<Void> trySendVerificationCodeRequestedNotifications(final Account destination, final Instant requestTimestamp) {
final List<CompletableFuture<?>> sendNotificationFutures = new ArrayList<>();
public CompletableFuture<SendPushNotificationResult> sendVerificationCodeRequestedNotifications(final Account destination, final Instant requestTimestamp)
throws NotPushRegisteredException {
for (final Device device : destination.getDevices()) {
try {
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(destination.getPrimaryDevice());
sendNotificationFutures.add(sendNotification(new PushNotification(tokenAndType.first(),
tokenAndType.second(),
PushNotification.NotificationType.VERIFICATION_CODE_REQUESTED,
new VerificationCodeRequestData(requestTimestamp.toEpochMilli()),
destination,
device,
true,
VERIFICATION_CODE_TTL)));
} catch (final NotPushRegisteredException _) {
}
}
return CompletableFuture.allOf(sendNotificationFutures.toArray(CompletableFuture[]::new));
return sendNotification(new PushNotification(tokenAndType.first(),
tokenAndType.second(),
PushNotification.NotificationType.VERIFICATION_CODE_REQUESTED,
new VerificationCodeRequestData(requestTimestamp.toEpochMilli()),
destination,
destination.getPrimaryDevice(),
true,
VERIFICATION_CODE_TTL))
.thenApply(maybeResponse -> maybeResponse.orElseThrow(
() -> new AssertionError("Responses must be present for urgent notifications")));
}
public void handleMessagesRetrieved(final Account account, final Device device, final String userAgent) {
@@ -75,6 +75,7 @@ import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptio
import org.whispersystems.textsecuregcm.mappers.ObsoletePhoneNumberFormatExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExceptionMapper;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.registration.RegistrationFraudException;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
@@ -1079,7 +1080,8 @@ class VerificationControllerTest {
@CartesianTest
void requestVerificationCodeSuccess(@CartesianTest.Values(booleans = {true, false}) final boolean accountExistsWithNumber,
@CartesianTest.Values(booleans = {true, false}) final boolean enrolledInExperiment) {
@CartesianTest.Values(booleans = {true, false}) final boolean enrolledInExperiment)
throws NotPushRegisteredException {
final String encodedSessionId = encodeSessionId(SESSION_ID);
final RegistrationServiceSession registrationServiceSession = new RegistrationServiceSession(SESSION_ID, NUMBER,
false, null, null,
@@ -1118,9 +1120,9 @@ class VerificationControllerTest {
assertTrue(verificationSessionResponse.requestedInformation().isEmpty());
if (accountExistsWithNumber && enrolledInExperiment) {
verify(pushNotificationManager).trySendVerificationCodeRequestedNotifications(existingAccount, clock.instant());
verify(pushNotificationManager).sendVerificationCodeRequestedNotifications(existingAccount, clock.instant());
} else {
verify(pushNotificationManager, never()).trySendVerificationCodeRequestedNotifications(any(), any());
verify(pushNotificationManager, never()).sendVerificationCodeRequestedNotifications(any(), any());
}
}
}