Skip to content

Commit 7e99a06

Browse files
authored
Merge pull request #751 from Iterable/feature/JWTImprovement
[MOB - 8993] - JWT part 2 with improvements
2 parents 7ae0cde + 63db963 commit 7e99a06

File tree

6 files changed

+96
-9
lines changed

6 files changed

+96
-9
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.iterable.iterableapi;
2+
3+
/**
4+
* Represents an auth failure object.
5+
*/
6+
public class AuthFailure {
7+
8+
/** userId or email of the signed-in user */
9+
public final String userKey;
10+
11+
/** the authToken which caused the failure */
12+
public final String failedAuthToken;
13+
14+
/** the timestamp of the failed request */
15+
public final long failedRequestTime;
16+
17+
/** indicates a reason for failure */
18+
public final AuthFailureReason failureReason;
19+
20+
public AuthFailure(String userKey,
21+
String failedAuthToken,
22+
long failedRequestTime,
23+
AuthFailureReason failureReason) {
24+
this.userKey = userKey;
25+
this.failedAuthToken = failedAuthToken;
26+
this.failedRequestTime = failedRequestTime;
27+
this.failureReason = failureReason;
28+
}
29+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.iterable.iterableapi;
2+
public enum AuthFailureReason {
3+
AUTH_TOKEN_EXPIRED,
4+
AUTH_TOKEN_GENERIC_ERROR,
5+
AUTH_TOKEN_EXPIRATION_INVALID,
6+
AUTH_TOKEN_SIGNATURE_INVALID,
7+
AUTH_TOKEN_FORMAT_INVALID,
8+
AUTH_TOKEN_INVALIDATED,
9+
AUTH_TOKEN_PAYLOAD_INVALID,
10+
AUTH_TOKEN_USER_KEY_INVALID,
11+
AUTH_TOKEN_NULL,
12+
AUTH_TOKEN_GENERATION_ERROR,
13+
AUTH_TOKEN_MISSING,
14+
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ private void retrieveEmailAndUserId() {
457457
if (_authToken != null) {
458458
getAuthManager().queueExpirationRefresh(_authToken);
459459
} else {
460-
IterableLogger.d(TAG, "Auth token found as null. Scheduling token refresh in 10 seconds...");
460+
IterableLogger.d(TAG, "Auth token found as null. Rescheduling auth token refresh");
461461
getAuthManager().scheduleAuthTokenRefresh(authManager.getNextRetryInterval(), true, null);
462462
}
463463
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
public interface IterableAuthHandler {
44
String onAuthTokenRequested();
55
void onTokenRegistrationSuccessful(String authToken);
6-
void onTokenRegistrationFailed(Throwable object);
6+
void onAuthFailure(AuthFailure authFailure);
77
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public synchronized void requestNewAuthToken(
7575
boolean hasFailedPriorAuth,
7676
final IterableHelper.SuccessHandler successCallback,
7777
boolean shouldIgnoreRetryPolicy) {
78-
if ((!shouldIgnoreRetryPolicy && pauseAuthRetry) || (retryCount >= authRetryPolicy.maxRetry && !shouldIgnoreRetryPolicy)) {
78+
if (!shouldIgnoreRetryPolicy && (pauseAuthRetry || (retryCount >= authRetryPolicy.maxRetry))) {
7979
return;
8080
}
8181

@@ -123,20 +123,20 @@ private void handleAuthTokenSuccess(String authToken, IterableHelper.SuccessHand
123123
}
124124
queueExpirationRefresh(authToken);
125125
} else {
126-
IterableLogger.w(TAG, "Auth token received as null. Calling the handler in 10 seconds");
127-
//TODO: Make this time configurable and in sync with SDK initialization flow for auth null scenario
126+
handleAuthFailure(authToken, AuthFailureReason.AUTH_TOKEN_NULL);
127+
IterableApi.getInstance().setAuthToken(authToken);
128128
scheduleAuthTokenRefresh(getNextRetryInterval(), false, null);
129-
authHandler.onTokenRegistrationFailed(new Throwable("Auth token null"));
130129
return;
131130
}
132131
IterableApi.getInstance().setAuthToken(authToken);
133132
reSyncAuth();
134133
authHandler.onTokenRegistrationSuccessful(authToken);
135134
}
136135

136+
// This method is called when there is an error receiving an the auth token.
137137
private void handleAuthTokenFailure(Throwable throwable) {
138138
IterableLogger.e(TAG, "Error while requesting Auth Token", throwable);
139-
authHandler.onTokenRegistrationFailed(throwable);
139+
handleAuthFailure(null, AuthFailureReason.AUTH_TOKEN_GENERATION_ERROR);
140140
pendingAuth = false;
141141
scheduleAuthTokenRefresh(getNextRetryInterval(), false, null);
142142
}
@@ -153,8 +153,8 @@ public void queueExpirationRefresh(String encodedJWT) {
153153
}
154154
} catch (Exception e) {
155155
IterableLogger.e(TAG, "Error while parsing JWT for the expiration", e);
156-
authHandler.onTokenRegistrationFailed(new Throwable("Auth token decode failure. Scheduling auth token refresh in 10 seconds..."));
157-
//TODO: Sync with configured time duration once feature is available.
156+
isLastAuthTokenValid = false;
157+
handleAuthFailure(encodedJWT, AuthFailureReason.AUTH_TOKEN_PAYLOAD_INVALID);
158158
scheduleAuthTokenRefresh(getNextRetryInterval(), false, null);
159159
}
160160
}
@@ -170,6 +170,14 @@ void reSyncAuth() {
170170
}
171171
}
172172

173+
// This method is called is used to call the authHandler.onAuthFailure method with appropriate AuthFailureReason
174+
void handleAuthFailure(String authToken, AuthFailureReason failureReason) {
175+
if (authHandler != null) {
176+
authHandler.onAuthFailure(new AuthFailure(getEmailOrUserId(), authToken, IterableUtil.currentTimeMillis(), failureReason));
177+
}
178+
}
179+
180+
173181
long getNextRetryInterval() {
174182
long nextRetryInterval = authRetryPolicy.retryInterval;
175183
if (authRetryPolicy.retryBackoff == RetryPolicy.Type.EXPONENTIAL) {
@@ -187,6 +195,7 @@ void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, fin
187195
if (timer == null) {
188196
timer = new Timer(true);
189197
}
198+
190199
try {
191200
timer.schedule(new TimerTask() {
192201
@Override

iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static IterableApiResponse executeApiRequest(IterableApiRequest iterableApiReque
190190
if (responseCode == 401) {
191191
if (matchesJWTErrorCodes(jsonResponse)) {
192192
apiResponse = IterableApiResponse.failure(responseCode, requestResult, jsonResponse, "JWT Authorization header error");
193+
IterableApi.getInstance().getAuthManager().handleAuthFailure(iterableApiRequest.authToken, getMappedErrorCodeForMessage(jsonResponse));
193194
// We handle the JWT Retry for both online and offline here rather than handling online request in onPostExecute
194195
requestNewAuthTokenAndRetry(iterableApiRequest);
195196
} else {
@@ -265,6 +266,40 @@ private static boolean matchesErrorCode(JSONObject jsonResponse, String errorCod
265266
}
266267
}
267268

269+
private static AuthFailureReason getMappedErrorCodeForMessage(JSONObject jsonResponse) {
270+
try {
271+
if (jsonResponse == null || !jsonResponse.has("msg")) {
272+
return null;
273+
}
274+
275+
String errorMessage = jsonResponse.getString("msg");
276+
277+
switch (errorMessage.toLowerCase()) {
278+
case "exp must be less than 1 year from iat":
279+
return AuthFailureReason.AUTH_TOKEN_EXPIRATION_INVALID;
280+
case "jwt format is invalid":
281+
return AuthFailureReason.AUTH_TOKEN_FORMAT_INVALID;
282+
case "jwt token is expired":
283+
return AuthFailureReason.AUTH_TOKEN_EXPIRED;
284+
case "jwt is invalid":
285+
return AuthFailureReason.AUTH_TOKEN_SIGNATURE_INVALID;
286+
case "jwt payload requires a value for userid or email":
287+
case "email could not be found":
288+
return AuthFailureReason.AUTH_TOKEN_USER_KEY_INVALID;
289+
case "jwt token has been invalidated":
290+
return AuthFailureReason.AUTH_TOKEN_INVALIDATED;
291+
case "invalid payload":
292+
return AuthFailureReason.AUTH_TOKEN_PAYLOAD_INVALID;
293+
case "jwt authorization header is not set":
294+
return AuthFailureReason.AUTH_TOKEN_MISSING;
295+
default:
296+
return AuthFailureReason.AUTH_TOKEN_GENERIC_ERROR;
297+
}
298+
} catch (JSONException e) {
299+
return null;
300+
}
301+
}
302+
268303
private static boolean matchesJWTErrorCodes(JSONObject jsonResponse) {
269304
return matchesErrorCode(jsonResponse, ERROR_CODE_INVALID_JWT_PAYLOAD) || matchesErrorCode(jsonResponse, ERROR_CODE_MISSING_JWT_PAYLOAD) || matchesErrorCode(jsonResponse, ERROR_CODE_JWT_USER_IDENTIFIERS_MISMATCHED);
270305
}

0 commit comments

Comments
 (0)