@@ -14,12 +14,22 @@ public class Client {
14
14
return Current . network. dataTask ( with: URLRequest . itcServiceKey)
15
15
. map ( \. data)
16
16
. decode ( type: ServiceKeyResponse . self, decoder: JSONDecoder ( ) )
17
- . flatMap { serviceKeyResponse -> AnyPublisher < URLSession . DataTaskPublisher . Output , Swift . Error > in
17
+ . flatMap { serviceKeyResponse -> AnyPublisher < ( String , String ) , Swift . Error > in
18
18
serviceKey = serviceKeyResponse. authServiceKey
19
- return Current . network. dataTask ( with: URLRequest . signIn ( serviceKey: serviceKey, accountName: accountName, password: password) )
20
- . mapError { $0 as Swift . Error }
19
+
20
+ // Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
21
+ // On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
22
+ // Without this addition, Apple ID's would get set to locked
23
+ return self . loadHashcash ( accountName: accountName, serviceKey: serviceKey)
24
+ . map { return ( serviceKey, $0) }
21
25
. eraseToAnyPublisher ( )
22
26
}
27
+ . flatMap { ( serviceKey, hashcash) -> AnyPublisher < URLSession . DataTaskPublisher . Output , Swift . Error > in
28
+
29
+ return Current . network. dataTask ( with: URLRequest . signIn ( serviceKey: serviceKey, accountName: accountName, password: password, hashcash: hashcash) )
30
+ . mapError { $0 as Swift . Error }
31
+ . eraseToAnyPublisher ( )
32
+ }
23
33
. flatMap { result -> AnyPublisher < AuthenticationState , Swift . Error > in
24
34
let ( data, response) = result
25
35
return Just ( data)
@@ -56,6 +66,44 @@ public class Client {
56
66
. mapError { $0 as Swift . Error }
57
67
. eraseToAnyPublisher ( )
58
68
}
69
+
70
+ func loadHashcash( accountName: String , serviceKey: String ) -> AnyPublisher < String , Swift . Error > {
71
+
72
+ Result {
73
+ try URLRequest . federate ( account: accountName, serviceKey: serviceKey)
74
+ }
75
+ . publisher
76
+ . flatMap { request in
77
+ Current . network. dataTask ( with: request)
78
+ . mapError { $0 as Error }
79
+ . tryMap { ( data, response) throws -> ( String ) in
80
+ guard let urlResponse = response as? HTTPURLResponse else {
81
+ throw AuthenticationError . invalidSession
82
+ }
83
+ switch urlResponse. statusCode {
84
+ case 200 ..< 300 :
85
+
86
+ let httpResponse = response as! HTTPURLResponse
87
+ guard let bitsString = httpResponse. allHeaderFields [ " X-Apple-HC-Bits " ] as? String , let bits = UInt ( bitsString) else {
88
+ throw AuthenticationError . invalidHashcash
89
+ }
90
+ guard let challenge = httpResponse. allHeaderFields [ " X-Apple-HC-Challenge " ] as? String else {
91
+ throw AuthenticationError . invalidHashcash
92
+ }
93
+ guard let hashcash = Hashcash ( ) . mint ( resource: challenge, bits: bits) else {
94
+ throw AuthenticationError . invalidHashcash
95
+ }
96
+ return ( hashcash)
97
+ case 400 , 401 :
98
+ throw AuthenticationError . invalidHashcash
99
+ case let code:
100
+ throw AuthenticationError . badStatusCode ( statusCode: code, data: data, response: urlResponse)
101
+ }
102
+ }
103
+ }
104
+ . eraseToAnyPublisher ( )
105
+
106
+ }
59
107
60
108
func handleTwoStepOrFactor( data: Data , response: URLResponse , serviceKey: String ) -> AnyPublisher < AuthenticationState , Swift . Error > {
61
109
let httpResponse = response as! HTTPURLResponse
@@ -190,6 +238,7 @@ public enum AuthenticationState: Equatable {
190
238
191
239
public enum AuthenticationError : Swift . Error , LocalizedError , Equatable {
192
240
case invalidSession
241
+ case invalidHashcash
193
242
case invalidUsernameOrPassword( username: String )
194
243
case incorrectSecurityCode
195
244
case unexpectedSignInResponse( statusCode: Int , message: String ? )
@@ -206,6 +255,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
206
255
switch self {
207
256
case . invalidSession:
208
257
return " Your authentication session is invalid. Try signing in again. "
258
+ case . invalidHashcash:
259
+ return " Could not create a hashcash for the session. "
209
260
case . invalidUsernameOrPassword:
210
261
return " Invalid username and password combination. "
211
262
case . incorrectSecurityCode:
0 commit comments