From ce61333b76b9bb5a91ec060a9625505585065f69 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Fri, 13 Mar 2026 14:24:10 +0000 Subject: [PATCH 1/3] Fixed retrigger of completeUserLogin on new auth token --- CHANGELOG.md | 6 ++ .../com/iterable/iterableapi/IterableApi.java | 55 ++++++++++++------- .../iterableapi/IterableAuthManager.java | 6 +- .../IterableApiAuthSecurityTests.java | 18 +++--- .../iterableapi/IterableApiAuthTests.java | 53 ++++++++++++++++++ 5 files changed, 108 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1432536b5..9a0be9f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Added `updateAuthToken(String)` method for updating the auth token without triggering login side effects (push registration, in-app sync, embedded sync). Use this when you only need to refresh the token for an already logged-in user. + +### Deprecated +- `setAuthToken(String)` is now deprecated. It still triggers login operations (push registration, in-app sync, embedded sync) for backward compatibility, but will be changed to only store the token in a future release. Migrate to `updateAuthToken(String)` to update the token without side effects, or use `setEmail(email, authToken)` / `setUserId(userId, authToken)` to set credentials and trigger login operations. + ## [3.7.0] - Replaced the deprecated `AsyncTask`-based push notification handling with `WorkManager` for improved reliability and compatibility with modern Android versions. No action is required. - Fixed lost event tracking and missed API calls with an auto-retry feature for JWT token failures. diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index c6a676e83..2da2216a3 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -137,7 +137,7 @@ public String getAuthToken() { private void checkAndUpdateAuthToken(@Nullable String authToken) { // If authHandler exists and if authToken is new, it will be considered as a call to update the authToken. if (config.authHandler != null && authToken != null && authToken != _authToken) { - setAuthToken(authToken); + updateAuthToken(authToken); } } @@ -425,20 +425,24 @@ private void onLogin( @Nullable IterableHelper.FailureHandler failureHandler ) { if (!isInitialized()) { - setAuthToken(null); + updateAuthToken(null); return; } getAuthManager().pauseAuthRetries(false); if (authToken != null) { - setAuthToken(authToken); + updateAuthToken(authToken); + completeUserLogin(); attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); } else { - getAuthManager().requestNewAuthToken(false, data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler)); + getAuthManager().requestNewAuthToken(false, data -> { + completeUserLogin(); + attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isUnknown, failureHandler); + }); } } - private void completeUserLogin() { + void completeUserLogin() { completeUserLogin(_email, _userId, _authToken); } @@ -679,19 +683,16 @@ public void resetAuth() { //region API functions (private/internal) //--------------------------------------------------------------------------------------- - void setAuthToken(String authToken, boolean bypassAuth) { + + /** + * Updates the auth token without triggering login side effects (push registration, in-app sync, etc.). + * Use this method when you only need to update the token for an already logged-in user. + * For initial login, use {@code setEmail(email, authToken)} or {@code setUserId(userId, authToken)}. + */ + public void updateAuthToken(@Nullable String authToken) { if (isInitialized()) { - if ((authToken != null && !authToken.equalsIgnoreCase(_authToken)) || (_authToken != null && !_authToken.equalsIgnoreCase(authToken))) { - _authToken = authToken; - // SECURITY: Use completion handler to atomically store and pass validated credentials. - // The completion handler receives exact values stored to keychain, preventing TOCTOU - // attacks where keychain could be modified between storage and completeUserLogin execution. - storeAuthData((email, userId, token) -> completeUserLogin(email, userId, token)); - } else if (bypassAuth) { - // SECURITY: Pass current credentials directly to completeUserLogin. - // completeUserLogin will validate authToken presence when JWT auth is enabled. - completeUserLogin(_email, _userId, _authToken); - } + _authToken = authToken; + storeAuthData(); } } @@ -1211,8 +1212,24 @@ private void attemptAndProcessMerge(@NonNull String destinationUser, boolean isE }); } - public void setAuthToken(String authToken) { - setAuthToken(authToken, false); + /** + * Sets the auth token and triggers login operations (push registration, in-app sync, embedded sync). + * + * @deprecated This method triggers login side effects beyond just setting the token. + * To update the auth token without login side effects, use {@link #updateAuthToken(String)}. + * To set credentials and trigger login operations, use {@code setEmail(email, authToken)} + * or {@code setUserId(userId, authToken)}. + * In a future release, this method will only store the auth token without triggering login operations. + */ + @Deprecated + public void setAuthToken(@Nullable String authToken) { + if (isInitialized()) { + IterableLogger.w(TAG, "setAuthToken() is deprecated. Use updateAuthToken() to update the token, " + + "or setEmail(email, authToken) / setUserId(userId, authToken) for login. " + + "In a future release, this method will only store the auth token without triggering login operations."); + _authToken = authToken; + storeAuthData(this::completeUserLogin); + } } /** diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java index 3a960e54a..513ba6182 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java @@ -204,7 +204,7 @@ public void run() { } } else { - IterableApi.getInstance().setAuthToken(null, true); + IterableApi.getInstance().completeUserLogin(); } } @@ -213,7 +213,7 @@ private void handleAuthTokenSuccess(String authToken, IterableHelper.SuccessHand // Token obtained but not yet verified by a request - set state to UNKNOWN. // setAuthState will notify listeners only if previous state was INVALID. setAuthState(AuthState.UNKNOWN); - IterableApi.getInstance().setAuthToken(authToken); + IterableApi.getInstance().updateAuthToken(authToken); queueExpirationRefresh(authToken); if (successCallback != null) { @@ -221,7 +221,7 @@ private void handleAuthTokenSuccess(String authToken, IterableHelper.SuccessHand } } else { handleAuthFailure(authToken, AuthFailureReason.AUTH_TOKEN_NULL); - IterableApi.getInstance().setAuthToken(authToken); + IterableApi.getInstance().updateAuthToken(authToken); scheduleAuthTokenRefresh(getNextRetryInterval(), false, null); return; } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java index 2fa168b59..4666639af 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java @@ -99,13 +99,13 @@ public void testCompleteUserLogin_WithJWTAuth_NoToken_SkipsSensitiveOps() throws when(api.getInAppManager()).thenReturn(mockInAppManager); when(api.getEmbeddedManager()).thenReturn(mockEmbeddedManager); - // Directly call setAuthToken with null and bypassAuth=true to simulate + // Directly call updateAuthToken with null to simulate // attempting to bypass with no token (user-controlled bypass scenario) - api.setAuthToken(null, true); + api.updateAuthToken(null); shadowOf(getMainLooper()).idle(); - // Verify sensitive operations were NOT called (JWT auth enabled, no token) + // Verify sensitive operations were NOT called (updateAuthToken only stores, no login side effects) verify(mockInAppManager, never()).syncInApp(); verify(mockEmbeddedManager, never()).syncMessages(); } @@ -246,14 +246,16 @@ public void testSetAuthToken_UsesCompletionHandlerPattern() throws Exception { org.mockito.Mockito.clearInvocations(mockInAppManager, mockEmbeddedManager); // Now update auth token (simulating token refresh) + // updateAuthToken just stores the token — it does not trigger completeUserLogin. + // Sensitive operations (syncInApp, syncMessages) are only triggered during login flow. final String newToken = "new_jwt_token_here"; - api.setAuthToken(newToken, false); + api.updateAuthToken(newToken); shadowOf(getMainLooper()).idle(); - // Verify sensitive operations were called with updated token - verify(mockInAppManager).syncInApp(); - verify(mockEmbeddedManager).syncMessages(); + // Verify sensitive operations were NOT called (updateAuthToken only stores, doesn't trigger login) + verify(mockInAppManager, never()).syncInApp(); + verify(mockEmbeddedManager, never()).syncMessages(); assertEquals("Token should be updated", newToken, api.getAuthToken()); } @@ -274,7 +276,7 @@ public void testSetAuthToken_BypassAuth_StillValidatesToken() throws Exception { when(api.getEmbeddedManager()).thenReturn(mockEmbeddedManager); // Try to bypass with no token set - api.setAuthToken(null, true); + api.updateAuthToken(null); shadowOf(getMainLooper()).idle(); diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java index 12d1b473b..135666de1 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java @@ -23,9 +23,14 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; + +import org.mockito.Mockito; import static org.robolectric.annotation.LooperMode.Mode.PAUSED; @LooperMode(PAUSED) @@ -509,4 +514,52 @@ public void testAuthTokenRefreshPausesOnBackground() throws Exception { // Test passes if no exceptions were thrown and lifecycle methods executed successfully } + @Test + public void testTokenRefreshDoesNotTriggerPushRegistration() throws Exception { + IterablePushRegistration.IterablePushRegistrationImpl originalPushImpl = IterablePushRegistration.instance; + IterablePushRegistration.instance = mock(IterablePushRegistration.IterablePushRegistrationImpl.class); + + try { + // Initialize with auth and auto push registration enabled + IterableApi.sharedInstance = new IterableApi(); + authHandler = mock(IterableAuthHandler.class); + IterableApi.initialize(getContext(), "apiKey", + new IterableConfig.Builder() + .setAuthHandler(authHandler) + .setAutoPushRegistration(true) + .setPushIntegrationName("pushIntegration") + .build()); + + // Initial login: setEmail triggers requestNewAuthToken on executor thread + doReturn(validJWT).when(authHandler).onAuthTokenRequested(); + IterableApi.getInstance().setEmail("test@example.com"); + // Allow executor thread to complete and main looper to process callbacks + Thread.sleep(500); + shadowOf(getMainLooper()).idle(); + shadowOf(getMainLooper()).runToEndOfTasks(); + + // Verify initial push registration happened + verify(IterablePushRegistration.instance).executePushRegistrationTask(any(IterablePushRegistrationData.class)); + + // Reset mock to clear invocation history + Mockito.reset(IterablePushRegistration.instance); + + // Trigger auth token refresh with a different JWT + doReturn(newJWT).when(authHandler).onAuthTokenRequested(); + IterableApi.getInstance().getAuthManager().requestNewAuthToken(false, null); + // Allow executor thread to complete and main looper to process callbacks + Thread.sleep(500); + shadowOf(getMainLooper()).idle(); + shadowOf(getMainLooper()).runToEndOfTasks(); + + // Verify the token was actually refreshed + assertEquals(newJWT, IterableApi.getInstance().getAuthToken()); + + // Assert that push registration was NOT called again on token refresh + verify(IterablePushRegistration.instance, never()).executePushRegistrationTask(any(IterablePushRegistrationData.class)); + } finally { + IterablePushRegistration.instance = originalPushImpl; + } + } + } From affca7b57e7177a44b4bc5e5b950223c4a4d31b0 Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Fri, 13 Mar 2026 19:09:37 +0000 Subject: [PATCH 2/3] On same email login also complete the login flow --- .../com/iterable/iterableapi/IterableApi.java | 6 +++ .../IterableApiAuthSecurityTests.java | 47 +++++++++++++++++++ .../iterableapi/IterableApiAuthTests.java | 5 ++ 3 files changed, 58 insertions(+) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 2da2216a3..6d3f232aa 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -1076,6 +1076,9 @@ public void setEmail(@Nullable String email, @Nullable String authToken, @Nullab if (_email != null && _email.equals(email)) { checkAndUpdateAuthToken(authToken); + _setUserSuccessCallbackHandler = successHandler; + _setUserFailureCallbackHandler = failureHandler; + onLogin(authToken, email, true, merge, replay, false, failureHandler); return; } @@ -1146,6 +1149,9 @@ public void setUserId(@Nullable String userId, @Nullable String authToken, @Null if (_userId != null && _userId.equals(userId)) { checkAndUpdateAuthToken(authToken); + _setUserSuccessCallbackHandler = successHandler; + _setUserFailureCallbackHandler = failureHandler; + onLogin(authToken, userId, false, merge, replay, isUnknown, failureHandler); return; } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java index 4666639af..29c5a5300 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthSecurityTests.java @@ -141,6 +141,53 @@ public void testCompleteUserLogin_WithJWTAuth_WithToken_ExecutesSensitiveOps() t verify(mockEmbeddedManager).syncMessages(); } + /** + * Regression test: calling setEmail with the same email that's already set (e.g. after app + * restart where email is restored from keychain) should still trigger the full login flow + * (request auth token, syncInApp, syncMessages). + * + * Previously, the same-email path called checkAndUpdateAuthToken(null) which did nothing, + * so no login side effects occurred. + */ + @Test + public void testSetEmail_SameEmail_StillTriggersLogin() throws Exception { + initIterableWithAuth(); + + dispatcher.enqueueResponse("/users/update", new MockResponse().setResponseCode(200).setBody("{}")); + doReturn(validJWT).when(authHandler).onAuthTokenRequested(); + + IterableApi api = spy(IterableApi.getInstance()); + IterableApi.sharedInstance = api; + + IterableInAppManager mockInAppManager = mock(IterableInAppManager.class); + IterableEmbeddedManager mockEmbeddedManager = mock(IterableEmbeddedManager.class); + when(api.getInAppManager()).thenReturn(mockInAppManager); + when(api.getEmbeddedManager()).thenReturn(mockEmbeddedManager); + + // First login — triggers full flow + api.setEmail("user@example.com"); + server.takeRequest(1, TimeUnit.SECONDS); + shadowOf(getMainLooper()).idle(); + + verify(mockInAppManager).syncInApp(); + verify(mockEmbeddedManager).syncMessages(); + + // Clear invocations so we can verify the second call independently + org.mockito.Mockito.clearInvocations(mockInAppManager, mockEmbeddedManager); + + // Enqueue another response for the second login's /users/update call + dispatcher.enqueueResponse("/users/update", new MockResponse().setResponseCode(200).setBody("{}")); + + // Second login with SAME email — simulates app restart where email is in keychain + api.setEmail("user@example.com"); + server.takeRequest(1, TimeUnit.SECONDS); + shadowOf(getMainLooper()).idle(); + + // This SHOULD still trigger login side effects + verify(mockInAppManager).syncInApp(); + verify(mockEmbeddedManager).syncMessages(); + } + /** * Test that completeUserLogin executes sensitive operations when JWT auth is NOT enabled, * even without an authToken. diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java index 135666de1..912eb1cf3 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthTests.java @@ -1,5 +1,7 @@ package com.iterable.iterableapi; +import android.content.Context; + import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.junit.After; @@ -46,6 +48,7 @@ public class IterableApiAuthTests extends BaseTest { @Before public void setUp() { + getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE).edit().clear().apply(); server = new MockWebServer(); dispatcher = new PathBasedQueueDispatcher(); @@ -521,6 +524,8 @@ public void testTokenRefreshDoesNotTriggerPushRegistration() throws Exception { try { // Initialize with auth and auto push registration enabled + // Clear keychain data from setUp so retrieveEmailAndUserId starts fresh + getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE).edit().clear().apply(); IterableApi.sharedInstance = new IterableApi(); authHandler = mock(IterableAuthHandler.class); IterableApi.initialize(getContext(), "apiKey", From 6d3a0b7abd40bd640084a6302abafedab79f43ef Mon Sep 17 00:00:00 2001 From: Franco Zalamena Date: Tue, 17 Mar 2026 10:40:33 +0000 Subject: [PATCH 3/3] Fix for auto retry on 401 error Now we are recreating the token and starting the auto retry --- .../iterableapi/IterableAuthManager.java | 70 ++++++++++++------- .../iterableapi/IterableRequestTask.java | 13 ++-- .../iterableapi/IterableTaskRunner.java | 2 +- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java index 513ba6182..085f885c4 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java @@ -55,6 +55,7 @@ interface AuthTokenReadyListener { private volatile boolean isInForeground = true; // Assume foreground initially private volatile AuthState authState = AuthState.UNKNOWN; + private final Object timerLock = new Object(); private final ArrayList authTokenReadyListeners = new ArrayList<>(); private final ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -95,6 +96,21 @@ void setAuthTokenInvalid() { setAuthState(AuthState.INVALID); } + /** + * Handles a server-side JWT rejection (401). Invalidates the current token, + * clears any pending refresh, and schedules a new token request using the retry policy. + * When the new token arrives, AuthTokenReadyListeners are notified via the + * INVALID → UNKNOWN state transition. + */ + void handleAuthTokenRejection() { + setAuthState(AuthState.INVALID); + setIsLastAuthTokenValid(false); + clearRefreshTimer(); + resetFailedAuth(); + long retryInterval = getNextRetryInterval(); + scheduleAuthTokenRefresh(retryInterval, false, null); + } + AuthState getAuthState() { return authState; } @@ -292,29 +308,31 @@ long getNextRetryInterval() { } void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableHelper.SuccessHandler successCallback) { - if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) { - // we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work - return; - } - if (timer == null) { - timer = new Timer(true); - } + synchronized (timerLock) { + if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) { + // we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work + return; + } + if (timer == null) { + timer = new Timer(true); + } - try { - timer.schedule(new TimerTask() { - @Override - public void run() { - if (api.getEmail() != null || api.getUserId() != null) { - api.getAuthManager().requestNewAuthToken(false, successCallback, isScheduledRefresh); - } else { - IterableLogger.w(TAG, "Email or userId is not available. Skipping token refresh"); + try { + timer.schedule(new TimerTask() { + @Override + public void run() { + if (api.getEmail() != null || api.getUserId() != null) { + api.getAuthManager().requestNewAuthToken(false, successCallback, isScheduledRefresh); + } else { + IterableLogger.w(TAG, "Email or userId is not available. Skipping token refresh"); + } + isTimerScheduled = false; } - isTimerScheduled = false; - } - }, timeDuration); - isTimerScheduled = true; - } catch (Exception e) { - IterableLogger.e(TAG, "timer exception: " + timer, e); + }, timeDuration); + isTimerScheduled = true; + } catch (Exception e) { + IterableLogger.e(TAG, "timer exception: " + timer, e); + } } } @@ -363,10 +381,12 @@ private void checkAndHandleAuthRefresh() { } void clearRefreshTimer() { - if (timer != null) { - timer.cancel(); - timer = null; - isTimerScheduled = false; + synchronized (timerLock) { + if (timer != null) { + timer.cancel(); + timer = null; + isTimerScheduled = false; + } } } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java index 33ecddd3f..08dfba9c2 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java @@ -262,18 +262,17 @@ static IterableApiResponse executeApiRequest(IterableApiRequest iterableApiReque } /** - * When autoRetry is enabled and this is an offline task, skip the inline retry. - * The task stays in the DB and IterableTaskRunner will retry it once a valid JWT - * is obtained via the AuthTokenReadyListener callback. + * When autoRetry is enabled and this is an offline task, do nothing here. + * IterableTaskRunner.processTask() is the sole owner of 401 handling for offline tasks: + * it calls setAuthTokenInvalid() which invalidates the token and schedules a refresh. + * When the new token arrives, onAuthTokenReady() resumes the queue. * For online requests or when autoRetry is disabled, use the existing inline retry. */ private static void handleJwtAuthRetry(IterableApiRequest iterableApiRequest) { boolean autoRetry = IterableApi.getInstance().isAutoRetryOnJwtFailure(); if (autoRetry && iterableApiRequest.getProcessorType() == IterableApiRequest.ProcessorType.OFFLINE) { - IterableAuthManager authManager = IterableApi.getInstance().getAuthManager(); - authManager.setIsLastAuthTokenValid(false); - long retryInterval = authManager.getNextRetryInterval(); - authManager.scheduleAuthTokenRefresh(retryInterval, false, null); + IterableLogger.d(TAG, "Offline task 401 - deferring retry to IterableTaskRunner"); + return; } else { requestNewAuthTokenAndRetry(iterableApiRequest); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java index 90b2d2dc2..96b25c9fd 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java @@ -197,7 +197,7 @@ private boolean processTask(@NonNull IterableTask task, boolean autoRetry) { // retain the task and pause processing until a valid JWT is obtained. if (autoRetry && isJwtFailure(response)) { IterableLogger.d(TAG, "JWT auth failure on task " + task.id + ". Retaining task and pausing processing."); - IterableApi.getInstance().getAuthManager().setAuthTokenInvalid(); + IterableApi.getInstance().getAuthManager().handleAuthTokenRejection(); isPausedForAuth = true; callTaskCompletedListeners(task.id, TaskResult.RETRY, response); return false;