From cb6123125f7156f19251002ec0df518dbee215e4 Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Fri, 22 May 2026 16:29:48 -0400 Subject: [PATCH] Only send verification code push notifications to primary devices --- .../controllers/VerificationController.java | 9 ++++-- .../push/PushNotificationManager.java | 31 ++++++++----------- .../VerificationControllerTest.java | 8 +++-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java index 6e914e5ff..7c96a3588 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java @@ -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) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushNotificationManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushNotificationManager.java index ddda2b76d..76b1bec23 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushNotificationManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushNotificationManager.java @@ -87,26 +87,21 @@ public class PushNotificationManager { .thenApply(maybeResponse -> maybeResponse.orElseThrow(() -> new AssertionError("Responses must be present for urgent notifications"))); } - public CompletableFuture trySendVerificationCodeRequestedNotifications(final Account destination, final Instant requestTimestamp) { - final List> sendNotificationFutures = new ArrayList<>(); + public CompletableFuture sendVerificationCodeRequestedNotifications(final Account destination, final Instant requestTimestamp) + throws NotPushRegisteredException { - for (final Device device : destination.getDevices()) { - try { - final Pair tokenAndType = getToken(device); + final Pair 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) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java index e1439af6f..7b3e77bb6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java @@ -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()); } } }