-
Notifications
You must be signed in to change notification settings - Fork 886
/
FetchUserPoolTokensOperation.swift
204 lines (174 loc) · 6.91 KB
/
FetchUserPoolTokensOperation.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//
// Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
//
import Foundation
import AWSCognitoIdentityProvider
class FetchUserPoolTokensOperation: AWSAsyncOperation {
// Used to identify the operation.
//
// Identifier is mainly used in logs to identify different
// operations and its lifecycle
private let identifier: String = UUID().uuidString
// Completion to be called when the operation completes.
//
private let completion: TokenCompletion
// Holds an instance of the user pool low level SDK
//
private let userPool: CognitoUserPoolBehavior
public weak var delegate: FetchUserPoolTokensDelegate?
private var tokenFetchState: TokenFetchState = .notStarted
// Synchronizes access to `tokenFetchState`. The state is mutated through this queue, we should
// make sure that the state is not mutated/read outside this queue.
private let stateQueue = DispatchQueue(
label: "com.amazonaws.awsmobileClient.tokenFetch.state",
target: DispatchQueue.global())
init(userPool: CognitoUserPoolBehavior = AWSUserPoolClientHelper.userPoolClient(for: "tokenFetchUserPool"),
completion: @escaping TokenCompletion) {
self.completion = completion
self.userPool = userPool
AWSMobileClientLogging.verbose("\(identifier) Created FetchUserPoolTokensOperation")
}
override func main() {
AWSMobileClientLogging.verbose("\(identifier) Start execution")
acceptEvent(.startOperation)
}
func acceptEvent(_ event: TokenFetchEvent) {
guard !self.isFinished else { return }
stateQueue.async { [weak self] in
guard let self = self else { return }
let newState = self.tokenFetchState.resolveState(event: event)
AWSMobileClientLogging.verbose("""
\(self.identifier) \(self.tokenFetchState): \(event) -> \(newState)
""")
if newState == self.tokenFetchState { return }
self.tokenFetchState = newState
// Based on the newState perform the effect
switch self.tokenFetchState {
case .fetching:
DispatchQueue.global().async {
self.fetchToken()
}
case .waitingForSignIn:
DispatchQueue.global().async {
self.delegate?.tokenFetchNeedsAuthentication(operation: self)
}
case .fetched(let tokens):
self.finishWithResult(tokens: tokens)
case .error(let internalError):
switch internalError {
case .unableToSignIn:
self.finishWithUnableToSignIn()
case .noUserFound:
self.finishWithNotSignedInError()
case .serviceError(let error):
self.finishWithError(error: error)
}
default:
break
}
}
}
func authStateChanged(_ state: UserState) {
switch state {
case .signedOut, .guest:
acceptEvent(.signedOut)
case .signedIn:
acceptEvent(.signedIn)
default:
break
}
}
private func fetchToken() {
AWSMobileClientLogging.verbose("\(self.identifier) Inside fetch token")
guard
!self.isCancelled
else {
AWSMobileClientLogging.verbose("\(self.identifier) Cancelled")
finish()
return
}
guard
let username = self.delegate?.getCurrentUsername(operation: self),
!username.isEmpty
else {
AWSMobileClientLogging.verbose("\(self.identifier) No User name")
self.acceptEvent(.noUserFound)
return
}
let user = self.userPool.getIdentityUser(username)
user.getUserPoolToken { [weak self] result in
guard let self = self else { return }
guard
!self.isCancelled
else {
AWSMobileClientLogging.verbose("\(self.identifier) Cancelled")
self.finish()
return
}
switch result {
case .success(let tokens):
self.acceptEvent(.tokenFetched(tokens))
case .failure(let error):
if (error as NSError).didTokenExpire {
AWSMobileClientLogging.verbose("\(self.identifier) Need re-authentication")
self.acceptEvent(.tokenExpired)
} else {
self.acceptEvent(.serviceError(error))
}
}
}
}
private func finishWithUnableToSignIn() {
let message = AWSMobileClientConstants.noValidSignInSession
let error = AWSMobileClientError.unableToSignIn(message: message)
finishWithError(error: error)
}
private func finishWithNotSignedInError() {
let message = AWSMobileClientConstants.notSignedInMessage
let error = AWSMobileClientError.notSignedIn(message: message)
finishWithError(error: error)
}
private func finishWithResult(tokens: Tokens) {
AWSMobileClientLogging.verbose("\(self.identifier) Success")
completion(tokens, nil)
finish()
}
private func finishWithError(error: Error) {
AWSMobileClientLogging.verbose("\(self.identifier) Error")
completion(nil, error)
finish()
}
}
extension NSError {
// The underlying SDK returns different errors when the user token is expired based on the signIn
// mechanism.
//
// In case if user is signedin via non-HostedUI method and if the error returned is
// `InvalidAuthenticationDelegate` it means that userPool client is trying to get
// authentication details, which means that the refresh token is expired.
// On the other hand if the user is signedin via HostedUI, the SDK returns
// `errorExpiredRefreshToken` when the token expire.
//
var didTokenExpire: Bool {
let authCode = AWSCognitoIdentityClientErrorType.providerClientErrorInvalidAuthenticationDelegate.rawValue
if domain == AWSCognitoIdentityProviderErrorDomain,
code == authCode {
return true
} else if domain == AWSCognitoAuthErrorDomain,
code == AWSCognitoAuthClientErrorType.errorExpiredRefreshToken.rawValue {
return true
}
return false
}
}