Skip to content

Commit 14b1774

Browse files
authored
Merge pull request #774 from Iterable/JWT_Improvement_Part2
Jwt improvement part2
2 parents b90f1bd + d949bc0 commit 14b1774

11 files changed

+121
-12
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@
404404
E9003E012BF4DF15004AB45B /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9003E002BF4DF15004AB45B /* RetryPolicy.swift */; };
405405
E9BF47962B46D5DC0033DB69 /* IterableEmbeddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9BF47952B46D5DC0033DB69 /* IterableEmbeddedView.swift */; };
406406
E9BF47982B46DEB30033DB69 /* IterableEmbeddedView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9BF47972B46DEB30033DB69 /* IterableEmbeddedView.xib */; };
407+
E9FF7FD12BFCBD90000409ED /* AuthFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FF7FD02BFCBD90000409ED /* AuthFailure.swift */; };
408+
E9FF7FD32BFCBDB9000409ED /* AuthFailureReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FF7FD22BFCBDB9000409ED /* AuthFailureReason.swift */; };
407409
/* End PBXBuildFile section */
408410

409411
/* Begin PBXContainerItemProxy section */
@@ -809,6 +811,8 @@
809811
E9003E002BF4DF15004AB45B /* RetryPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryPolicy.swift; sourceTree = "<group>"; };
810812
E9BF47952B46D5DC0033DB69 /* IterableEmbeddedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IterableEmbeddedView.swift; sourceTree = "<group>"; };
811813
E9BF47972B46DEB30033DB69 /* IterableEmbeddedView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IterableEmbeddedView.xib; sourceTree = "<group>"; };
814+
E9FF7FD02BFCBD90000409ED /* AuthFailure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFailure.swift; sourceTree = "<group>"; };
815+
E9FF7FD22BFCBDB9000409ED /* AuthFailureReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFailureReason.swift; sourceTree = "<group>"; };
812816
/* End PBXFileReference section */
813817

814818
/* Begin PBXFrameworksBuildPhase section */
@@ -1065,6 +1069,8 @@
10651069
AC3C10F8213F46A900A9B839 /* IterableLogging.swift */,
10661070
ACA8D1A221910C66001B1332 /* IterableMessaging.swift */,
10671071
AC78F0E6253D7F09006378A5 /* IterablePushNotificationMetadata.swift */,
1072+
E9FF7FD02BFCBD90000409ED /* AuthFailure.swift */,
1073+
E9FF7FD22BFCBDB9000409ED /* AuthFailureReason.swift */,
10681074
E9003E002BF4DF15004AB45B /* RetryPolicy.swift */,
10691075
);
10701076
path = "swift-sdk";
@@ -2121,6 +2127,7 @@
21212127
551FA7582988A8FC0072D0A9 /* IterableEmbeddedManagerProtocol.swift in Sources */,
21222128
AC776DA6211A1B8A00C27C27 /* IterableRequestUtil.swift in Sources */,
21232129
55DD2053269FA28200773CC7 /* IterableInAppManagerProtocol.swift in Sources */,
2130+
E9FF7FD32BFCBDB9000409ED /* AuthFailureReason.swift in Sources */,
21242131
ACEDF41D2183C2EC000B9BFE /* Pending.swift in Sources */,
21252132
552A0AA7280E1FDA00A80963 /* DeepLinkManager.swift in Sources */,
21262133
E9BF47962B46D5DC0033DB69 /* IterableEmbeddedView.swift in Sources */,
@@ -2153,6 +2160,7 @@
21532160
AC347B5C20E5A7E1003449CF /* APNSTypeChecker.swift in Sources */,
21542161
ACD2B84F25B15CFA005D7A90 /* RequestSender.swift in Sources */,
21552162
AC3A336D24F65579008225BA /* RequestProcessorUtil.swift in Sources */,
2163+
E9FF7FD12BFCBD90000409ED /* AuthFailure.swift in Sources */,
21562164
55DD2041269FA24400773CC7 /* IterableInAppMessage.swift in Sources */,
21572165
AC8E7CA524C7555E0039605F /* CoreDataUtil.swift in Sources */,
21582166
AC1AA1C624EBB2DC00F29C6B /* IterableTaskRunner.swift in Sources */,

swift-sdk/AuthFailure.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// AuthFailure.swift
3+
// swift-sdk
4+
//
5+
// Created by HARDIK MASHRU on 21/05/24.
6+
// Copyright © 2024 Iterable. All rights reserved.
7+
//
8+
9+
import Foundation
10+
@objc public class AuthFailure: NSObject {
11+
12+
/// userId or email of the signed-in user
13+
public let userKey: String?
14+
15+
/// the authToken which caused the failure
16+
public let failedAuthToken: String?
17+
18+
/// the timestamp of the failed request
19+
public let failedRequestTime: Int
20+
21+
/// indicates a reason for failure
22+
public let failureReason: AuthFailureReason
23+
24+
public init(userKey: String?,
25+
failedAuthToken: String?,
26+
failedRequestTime: Int,
27+
failureReason: AuthFailureReason) {
28+
self.userKey = userKey
29+
self.failedAuthToken = failedAuthToken
30+
self.failedRequestTime = failedRequestTime
31+
self.failureReason = failureReason
32+
}
33+
}

swift-sdk/AuthFailureReason.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// AuthFailureReason.swift
3+
// swift-sdk
4+
//
5+
// Created by HARDIK MASHRU on 21/05/24.
6+
// Copyright © 2024 Iterable. All rights reserved.
7+
//
8+
9+
import Foundation
10+
@objc public enum AuthFailureReason: Int {
11+
case authTokenExpired
12+
case authTokenGenericError
13+
case authTokenExpirationInvalid
14+
case authTokenSignatureInvalid
15+
case authTokenFormatInvalid
16+
case authTokenInvalidated
17+
case authTokenPayloadInvalid
18+
case authTokenUserKeyInvalid
19+
case authTokenNull
20+
case authTokenGenerationError
21+
case authTokenMissing
22+
case authTokenInvalid
23+
}

swift-sdk/Internal/AuthManager.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,7 @@ class AuthManager: IterableAuthManagerProtocol {
156156
onSuccess?(authToken)
157157
}
158158
} else {
159-
delegate?.onTokenRegistrationFailed("auth token was nil, scheduling auth token retrieval in \(getNextRetryInterval()) seconds")
160-
161-
/// by default, schedule a refresh for 10s
159+
handleAuthFailure(failedAuthToken: nil, reason: .authTokenNull)
162160
scheduleAuthTokenRefreshTimer(interval: getNextRetryInterval(), successCallback: onSuccess)
163161
}
164162

@@ -167,13 +165,17 @@ class AuthManager: IterableAuthManagerProtocol {
167165
storeAuthToken()
168166
}
169167

168+
func handleAuthFailure(failedAuthToken: String?, reason: AuthFailureReason) {
169+
delegate?.onAuthFailure(AuthFailure(userKey: IterableUtil.getEmailOrUserId(), failedAuthToken: failedAuthToken, failedRequestTime: IterableUtil.secondsFromEpoch(for: dateProvider.currentDate), failureReason: reason))
170+
}
171+
170172
private func queueAuthTokenExpirationRefresh(_ authToken: String?, onSuccess: AuthTokenRetrievalHandler? = nil) -> Bool {
171173
ITBInfo()
172174

173175
clearRefreshTimer()
174176

175177
guard let authToken = authToken, let expirationDate = AuthManager.decodeExpirationDateFromAuthToken(authToken) else {
176-
delegate?.onTokenRegistrationFailed("auth token was nil or could not decode an expiration date, scheduling auth token retrieval in 10 seconds")
178+
handleAuthFailure(failedAuthToken: authToken, reason: .authTokenPayloadInvalid)
177179

178180
/// schedule a default timer of 10 seconds if we fall into this case
179181
scheduleAuthTokenRefreshTimer(interval: getNextRetryInterval(), successCallback: onSuccess)

swift-sdk/Internal/IterableUtil.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ import UIKit
5151
static func secondsFromEpoch(for date: Date) -> Int {
5252
Int(date.timeIntervalSince1970)
5353
}
54+
55+
static func getEmailOrUserId() -> String? {
56+
let email = IterableAPI.email
57+
let userId = IterableAPI.userId
58+
if email != nil {
59+
return email
60+
} else if userId != nil {
61+
return userId
62+
}
63+
return nil
64+
}
5465

5566
// given "var1", "val1", "var2", "val2" as input
5667
// this will return "var1: val1, var2: val2"

swift-sdk/Internal/OfflineRequestProcessor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ struct OfflineRequestProcessor: RequestProcessorProtocol {
350350
withIdentifier: identifier)
351351
result.onError { error in
352352
if error.httpStatusCode == 401, RequestProcessorUtil.matchesJWTErrorCode(error.iterableCode) {
353+
authManager?.handleAuthFailure(failedAuthToken: authManager?.getAuthToken(), reason: RequestProcessorUtil.getMappedErrorCodeForMessage(error.reason ?? ""))
353354
authManager?.setIsLastAuthTokenValid(false)
354355
let retryInterval = authManager?.getNextRetryInterval() ?? 1
355356
DispatchQueue.main.async {

swift-sdk/Internal/RequestProcessorUtil.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct RequestProcessorUtil {
1919
.onError { error in
2020
if error.httpStatusCode == 401, matchesJWTErrorCode(error.iterableCode) {
2121
ITBError("invalid JWT token, trying again: \(error.reason ?? "")")
22+
authManager?.handleAuthFailure(failedAuthToken: authManager?.getAuthToken(), reason: getMappedErrorCodeForMessage(error.reason ?? ""))
2223
authManager?.setIsLastAuthTokenValid(false)
2324
let retryInterval = authManager?.getNextRetryInterval() ?? 1
2425
DispatchQueue.main.async {
@@ -56,7 +57,6 @@ struct RequestProcessorUtil {
5657
} else if error.httpStatusCode == 401, error.iterableCode == JsonValue.Code.badApiKey {
5758
ITBError(error.reason)
5859
}
59-
6060
if let onFailure = onFailure {
6161
onFailure(error.reason, error.data)
6262
} else {
@@ -78,6 +78,30 @@ struct RequestProcessorUtil {
7878
}
7979
result.resolve(with: value)
8080
}
81+
82+
public static func getMappedErrorCodeForMessage(_ reason: String) -> AuthFailureReason {
83+
84+
switch reason.lowercased() {
85+
case "exp must be less than 1 year from iat":
86+
return .authTokenExpirationInvalid
87+
case "jwt format is invalid":
88+
return .authTokenFormatInvalid
89+
case "jwt token is expired":
90+
return .authTokenExpired
91+
case "jwt is invalid":
92+
return .authTokenSignatureInvalid
93+
case "jwt payload requires a value for userid or email", "email could not be found":
94+
return .authTokenUserKeyInvalid
95+
case "jwt token has been invalidated":
96+
return .authTokenInvalidated
97+
case "invalid payload":
98+
return .authTokenPayloadInvalid
99+
case "jwt authorization header is not set":
100+
return .authTokenMissing
101+
default:
102+
return .authTokenInvalid
103+
}
104+
}
81105

82106
private static func reportFailure(result: Fulfill<SendRequestValue, SendRequestError>,
83107
error: SendRequestError,

swift-sdk/IterableAuthManagerProtocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Foundation
1111
func scheduleAuthTokenRefreshTimer(interval: TimeInterval, isScheduledRefresh: Bool, successCallback: AuthTokenRetrievalHandler?)
1212
func setNewToken(_ newToken: String)
1313
func logoutUser()
14+
func handleAuthFailure(failedAuthToken: String?, reason: AuthFailureReason)
1415
func pauseAuthRetries(_ pauseAuthRetry: Bool)
1516
func setIsLastAuthTokenValid(_ isValid: Bool)
1617
func getNextRetryInterval() -> Double

swift-sdk/IterableConfig.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import Foundation
5555
/// The delegate for getting the authentication token
5656
@objc public protocol IterableAuthDelegate: AnyObject {
5757
@objc func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler)
58-
@objc func onTokenRegistrationFailed(_ reason: String?)
58+
@objc func onAuthFailure(_ authFailure: AuthFailure)
5959
}
6060

6161
/// Iterable Configuration Object. Use this when initializing the API.

tests/common/MockAuthManager.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import Foundation
99
@testable import IterableSDK
1010

1111
class MockAuthManager: IterableAuthManagerProtocol {
12+
13+
var shouldRetry = true
14+
var retryWasRequested = false
15+
16+
func handleAuthFailure(failedAuthToken: String?, reason: IterableSDK.AuthFailureReason) {
17+
18+
}
19+
1220
func requestNewAuthToken(hasFailedPriorAuth: Bool, onSuccess: ((String?) -> Void)?, shouldIgnoreRetryPolicy: Bool) {
1321
if shouldRetry {
1422
// Simulate the authManager obtaining a new token
@@ -38,8 +46,6 @@ class MockAuthManager: IterableAuthManagerProtocol {
3846
return 0
3947
}
4048

41-
var shouldRetry = true
42-
var retryWasRequested = false
4349

4450
func getAuthToken() -> String? {
4551
return "AuthToken"

tests/unit-tests/AuthTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ class AuthTests: XCTestCase {
684684
}
685685
}
686686

687-
func onTokenRegistrationFailed(_ reason: String?) {
687+
func onAuthFailure(_ authFailure: AuthFailure) {
688688

689689
}
690690
}
@@ -717,7 +717,7 @@ class AuthTests: XCTestCase {
717717
completion(AuthTests.authToken)
718718
}
719719

720-
func onTokenRegistrationFailed(_ reason: String?) {
720+
func onAuthFailure(_ authFailure: AuthFailure) {
721721

722722
}
723723
}
@@ -768,7 +768,7 @@ class AuthTests: XCTestCase {
768768
}
769769
}
770770

771-
func onTokenRegistrationFailed(_ reason: String?) {
771+
func onAuthFailure(_ authFailure: AuthFailure) {
772772

773773
}
774774
}
@@ -907,7 +907,7 @@ class AuthTests: XCTestCase {
907907
completion(authTokenGenerator())
908908
}
909909

910-
func onTokenRegistrationFailed(_ reason: String?) {
910+
func onAuthFailure(_ authFailure: AuthFailure) {
911911

912912
}
913913
}

0 commit comments

Comments
 (0)