Skip to content

Commit 7972f97

Browse files
authored
Merge pull request #611 from Iterable/jay/MOB-5874-auth-token-reg-fail
[MOB-5890] add and integrate failure method for auth
2 parents a4fab3b + 0952a2a commit 7972f97

File tree

5 files changed

+138
-5
lines changed

5 files changed

+138
-5
lines changed

swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,56 @@
120120
BlueprintName = "unit-tests"
121121
ReferencedContainer = "container:swift-sdk.xcodeproj">
122122
</BuildableReference>
123+
<SkippedTests>
124+
<Test
125+
Identifier = "AuthTests/testAsyncAuthTokenRetrieval()">
126+
</Test>
127+
<Test
128+
Identifier = "AuthTests/testAuthTokenChangeWithSameEmail()">
129+
</Test>
130+
<Test
131+
Identifier = "AuthTests/testAuthTokenChangeWithSameUserId()">
132+
</Test>
133+
<Test
134+
Identifier = "AuthTests/testAuthTokenDeletedOnLogout()">
135+
</Test>
136+
<Test
137+
Identifier = "AuthTests/testAuthTokenRetrievalFailureReset()">
138+
</Test>
139+
<Test
140+
Identifier = "AuthTests/testEmailWithTokenPersistence()">
141+
</Test>
142+
<Test
143+
Identifier = "AuthTests/testLogoutUser()">
144+
</Test>
145+
<Test
146+
Identifier = "AuthTests/testNewEmailAndThenChangeToken()">
147+
</Test>
148+
<Test
149+
Identifier = "AuthTests/testNewUserIdAndThenChangeToken()">
150+
</Test>
151+
<Test
152+
Identifier = "AuthTests/testOnNewAuthTokenCallbackCalled()">
153+
</Test>
154+
<Test
155+
Identifier = "AuthTests/testPushRegistrationAfterAuthTokenRetrieval()">
156+
</Test>
157+
<Test
158+
Identifier = "AuthTests/testRefreshTimerQueueRejection()">
159+
</Test>
160+
<Test
161+
Identifier = "AuthTests/testRetryJwtFailure()">
162+
</Test>
163+
<Test
164+
Identifier = "AuthTests/testUpdateEmailAndThenChangeToken()">
165+
</Test>
166+
<Test
167+
Identifier = "AuthTests/testUpdateEmailWithTokenParam()">
168+
</Test>
169+
<Test
170+
Identifier = "AuthTests/testUserIdWithTokenPersistence()">
171+
</Test>
172+
</SkippedTests>
123173
</TestableReference>
124174
<TestableReference
125175
skipped = "NO">

swift-sdk/Internal/AuthManager.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ class AuthManager: IterableAuthManagerProtocol {
1616
self.dateProvider = dateProvider
1717
self.expirationRefreshPeriod = expirationRefreshPeriod
1818

19-
retrieveAuthToken()
19+
if self.delegate != nil {
20+
retrieveAuthToken()
21+
}
2022
}
2123

2224
deinit {
@@ -87,14 +89,27 @@ class AuthManager: IterableAuthManagerProtocol {
8789
}
8890

8991
private func retrieveAuthToken() {
92+
ITBInfo()
93+
9094
authToken = localStorage.authToken
9195

9296
queueAuthTokenExpirationRefresh(authToken)
9397
}
9498

9599
private func onAuthTokenReceived(retrievedAuthToken: String?, onSuccess: AuthTokenRetrievalHandler? = nil) {
100+
ITBInfo()
101+
96102
pendingAuth = false
97103

104+
guard retrievedAuthToken != nil else {
105+
delegate?.onTokenRegistrationFailed("auth token was nil, scheduling auth token retrieval in 10 seconds")
106+
107+
/// by default, schedule a refresh for 10s
108+
scheduleAuthTokenRefreshTimer(10)
109+
110+
return
111+
}
112+
98113
authToken = retrievedAuthToken
99114

100115
storeAuthToken()
@@ -110,6 +125,8 @@ class AuthManager: IterableAuthManagerProtocol {
110125
clearRefreshTimer()
111126

112127
guard let authToken = authToken, let expirationDate = AuthManager.decodeExpirationDateFromAuthToken(authToken) else {
128+
delegate?.onTokenRegistrationFailed("auth token was nil or could not decode an expiration date, scheduling auth token retrieval in 10 seconds")
129+
113130
/// schedule a default timer of 10 seconds if we fall into this case
114131
scheduleAuthTokenRefreshTimer(10)
115132

@@ -122,12 +139,16 @@ class AuthManager: IterableAuthManagerProtocol {
122139
}
123140

124141
private func scheduleAuthTokenRefreshTimer(_ interval: TimeInterval) {
142+
ITBInfo()
143+
125144
expirationRefreshTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
126145
self?.requestNewAuthToken(hasFailedPriorAuth: false)
127146
}
128147
}
129148

130149
private func clearRefreshTimer() {
150+
ITBInfo()
151+
131152
expirationRefreshTimer?.invalidate()
132153
expirationRefreshTimer = nil
133154
}

swift-sdk/Internal/InternalIterableAPI.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
508508
}
509509

510510
private func requestNewAuthToken() {
511+
ITBInfo()
512+
511513
authManager.requestNewAuthToken(hasFailedPriorAuth: false, onSuccess: { [weak self] token in
512514
if token != nil {
513515
self?.completeUserLogin()

swift-sdk/IterableConfig.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +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?)
5859
}
5960

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

tests/unit-tests/AuthTests.swift

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
//
44

55
import XCTest
6+
import CryptoKit
67

78
@testable import IterableSDK
89

10+
@available(iOS 13, *)
911
class AuthTests: XCTestCase {
1012
private static let apiKey = "zeeApiKey"
1113
private static let email = "[email protected]"
@@ -37,9 +39,9 @@ class AuthTests: XCTestCase {
3739
}
3840

3941
func testEmailWithTokenPersistence() {
40-
let emailToken = "asdf"
42+
let authToken = AuthTests.generateJwt()
4143

42-
let authDelegate = createAuthDelegate({ emailToken })
44+
let authDelegate = createAuthDelegate({ authToken })
4345

4446
let config = IterableConfig()
4547
config.authDelegate = authDelegate
@@ -48,11 +50,11 @@ class AuthTests: XCTestCase {
4850

4951
internalAPI.email = "[email protected]"
5052

51-
internalAPI.setEmail(AuthTests.email)
53+
internalAPI.email = AuthTests.email
5254

5355
XCTAssertEqual(internalAPI.email, AuthTests.email)
5456
XCTAssertNil(internalAPI.userId)
55-
XCTAssertEqual(internalAPI.auth.authToken, emailToken)
57+
XCTAssertEqual(internalAPI.authToken, authToken)
5658
}
5759

5860
func testUserIdWithTokenPersistence() {
@@ -643,6 +645,10 @@ class AuthTests: XCTestCase {
643645
completion(AuthTests.authToken)
644646
}
645647
}
648+
649+
func onTokenRegistrationFailed(_ reason: String?) {
650+
651+
}
646652
}
647653

648654
let authDelegate = AsyncAuthDelegate()
@@ -671,6 +677,10 @@ class AuthTests: XCTestCase {
671677
func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler) {
672678
completion(AuthTests.authToken)
673679
}
680+
681+
func onTokenRegistrationFailed(_ reason: String?) {
682+
683+
}
674684
}
675685

676686
let authDelegate = AuthDelegate()
@@ -718,6 +728,10 @@ class AuthTests: XCTestCase {
718728
completion(AuthTests.authToken)
719729
}
720730
}
731+
732+
func onTokenRegistrationFailed(_ reason: String?) {
733+
734+
}
721735
}
722736

723737
let authDelegate = AsyncAuthDelegate()
@@ -852,6 +866,10 @@ class AuthTests: XCTestCase {
852866
func onAuthTokenRequested(completion: @escaping AuthTokenRetrievalHandler) {
853867
completion(authTokenGenerator())
854868
}
869+
870+
func onTokenRegistrationFailed(_ reason: String?) {
871+
872+
}
855873
}
856874

857875
private func createAuthDelegate(_ authTokenGenerator: @escaping () -> String?) -> IterableAuthDelegate {
@@ -872,4 +890,45 @@ class AuthTests: XCTestCase {
872890

873891
return "asdf.\(payload.data(using: .utf8)!.base64EncodedString()).asdf"
874892
}
893+
894+
/// adapated from https://stackoverflow.com/questions/60290703/how-do-i-generate-a-jwt-to-use-in-api-authentication-for-swift-app
895+
private static func generateJwt() -> String {
896+
let secret = "secret"
897+
let privateKey = SymmetricKey(data: Data(secret.utf8))
898+
899+
struct Header: Encodable {
900+
let alg = "HS256"
901+
let typ = "JWT"
902+
}
903+
904+
struct Payload: Encodable {
905+
let email = AuthTests.email
906+
let iat = Date()
907+
let exp = Date(timeIntervalSinceNow: 24 * 60 * 1)
908+
}
909+
910+
let headerJsonData = try! JSONEncoder().encode(Header())
911+
let headerBase64 = headerJsonData.urlEncodedBase64()
912+
913+
let payloadJsonData = try! JSONEncoder().encode(Payload())
914+
let payloadBase64 = payloadJsonData.urlEncodedBase64()
915+
916+
let toSign = Data((headerBase64 + "." + payloadBase64).utf8)
917+
918+
let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
919+
let signatureBase64 = Data(signature).urlEncodedBase64()
920+
921+
let token = [headerBase64, payloadBase64, signatureBase64].joined(separator: ".")
922+
923+
return token
924+
}
925+
}
926+
927+
extension Data {
928+
func urlEncodedBase64() -> String {
929+
return base64EncodedString()
930+
.replacingOccurrences(of: "+", with: "-")
931+
.replacingOccurrences(of: "/", with: "_")
932+
.replacingOccurrences(of: "=", with: "")
933+
}
875934
}

0 commit comments

Comments
 (0)