@@ -14,12 +14,22 @@ public class Client {
1414 return Current . network. dataTask ( with: URLRequest . itcServiceKey)
1515 . map ( \. data)
1616 . 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
1818 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) }
2125 . eraseToAnyPublisher ( )
2226 }
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+ }
2333 . flatMap { result -> AnyPublisher < AuthenticationState , Swift . Error > in
2434 let ( data, response) = result
2535 return Just ( data)
@@ -56,6 +66,44 @@ public class Client {
5666 . mapError { $0 as Swift . Error }
5767 . eraseToAnyPublisher ( )
5868 }
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+ }
59107
60108 func handleTwoStepOrFactor( data: Data , response: URLResponse , serviceKey: String ) -> AnyPublisher < AuthenticationState , Swift . Error > {
61109 let httpResponse = response as! HTTPURLResponse
@@ -190,6 +238,7 @@ public enum AuthenticationState: Equatable {
190238
191239public enum AuthenticationError : Swift . Error , LocalizedError , Equatable {
192240 case invalidSession
241+ case invalidHashcash
193242 case invalidUsernameOrPassword( username: String )
194243 case incorrectSecurityCode
195244 case unexpectedSignInResponse( statusCode: Int , message: String ? )
@@ -206,6 +255,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
206255 switch self {
207256 case . invalidSession:
208257 return " Your authentication session is invalid. Try signing in again. "
258+ case . invalidHashcash:
259+ return " Could not create a hashcash for the session. "
209260 case . invalidUsernameOrPassword:
210261 return " Invalid username and password combination. "
211262 case . incorrectSecurityCode:
0 commit comments