11import Foundation
2+
23#if canImport(FoundationNetworking)
3- import FoundationNetworking
4+ import FoundationNetworking
45#endif
56
67/// Find the spec here: https://atproto.com/specs/oauth
@@ -118,10 +119,15 @@ public enum Bluesky {
118119 }
119120 }
120121
121- private static func loginProvider( server: ServerMetadata , validator: @escaping TokenSubscriberValidator ) -> TokenHandling . LoginProvider {
122+ private static func loginProvider(
123+ server: ServerMetadata , validator: @escaping TokenSubscriberValidator
124+ ) -> TokenHandling . LoginProvider {
122125 return { params in
123126 // decode the params in the redirectURL
124- guard let redirectComponents = URLComponents ( url: params. redirectURL, resolvingAgainstBaseURL: false ) else {
127+ guard
128+ let redirectComponents = URLComponents (
129+ url: params. redirectURL, resolvingAgainstBaseURL: false )
130+ else {
125131 throw AuthenticatorError . missingTokenURL
126132 }
127133
@@ -141,11 +147,6 @@ public enum Bluesky {
141147 throw AuthenticatorError . issuingServerMismatch ( iss, server. issuer)
142148 }
143149
144- // and use them (plus just a little more) to construct the token request
145- guard let tokenURL = URL ( string: server. tokenEndpoint) else {
146- throw AuthenticatorError . missingTokenURL
147- }
148-
149150 guard let verifier = params. pcke? . verifier else {
150151 throw AuthenticatorError . pkceRequired
151152 }
@@ -158,76 +159,89 @@ public enum Bluesky {
158159 client_id: params. credentials. clientId
159160 )
160161
161- var request = URLRequest ( url: tokenURL)
162-
163- request. httpMethod = " POST "
164- request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
165- request. setValue ( " application/json " , forHTTPHeaderField: " Accept " )
166- request. httpBody = try JSONEncoder ( ) . encode ( tokenRequest)
167-
168- let ( data, _) = try await params. responseProvider ( request)
169-
170- let tokenResponse = try JSONDecoder ( ) . decode ( TokenResponse . self, from: data)
171-
172- guard tokenResponse. token_type == " DPoP " else {
173- throw AuthenticatorError . dpopTokenExpected ( tokenResponse. token_type)
174- }
175-
176- if try await validator ( tokenResponse, server. issuer) == false {
177- throw AuthenticatorError . tokenInvalid
178- }
179-
180- return tokenResponse. login ( for: iss)
162+ return try await Bluesky . requestToken (
163+ tokenRequest,
164+ authorizationServer: server,
165+ validator: validator,
166+ responseProvider: params. responseProvider
167+ )
181168 }
182169 }
183170
184- private static func refreshProvider( server: ServerMetadata , validator: @escaping TokenSubscriberValidator ) -> TokenHandling . RefreshProvider {
171+ private static func refreshProvider(
172+ server: ServerMetadata , validator: @escaping TokenSubscriberValidator
173+ ) -> TokenHandling . RefreshProvider {
185174 { login, credentials, responseProvider -> Login in
186175 guard let refreshToken = login. refreshToken? . value else {
187176 throw AuthenticatorError . refreshNotPossible
188177 }
189178
190- guard let tokenURL = URL ( string: server. tokenEndpoint) else {
191- throw AuthenticatorError . missingTokenURL
192- }
193-
194179 let tokenRequest = RefreshTokenRequest (
195180 refresh_token: refreshToken,
196181 redirect_uri: credentials. callbackURL. absoluteString,
197182 grant_type: " refresh_token " ,
198183 client_id: credentials. clientId
199184 )
200185
201- var request = URLRequest ( url: tokenURL)
202-
203- request. httpMethod = " POST "
204- request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
205- request. httpBody = try JSONEncoder ( ) . encode ( tokenRequest)
206-
207- let ( data, response) = try await responseProvider ( request)
186+ return try await Bluesky . requestToken (
187+ tokenRequest,
188+ authorizationServer: server,
189+ validator: validator,
190+ responseProvider: responseProvider
191+ )
192+ }
193+ }
208194
209- // make sure that we got a successful HTTP response
210- guard
211- let httpResponse = response as? HTTPURLResponse ,
212- httpResponse. statusCode >= 200 && httpResponse. statusCode < 300
213- else {
214- print ( " data: " , String ( decoding: data, as: UTF8 . self) )
215- print ( " response: " , response)
195+ private static func requestToken(
196+ _ tokenRequest: Encodable ,
197+ authorizationServer: ServerMetadata ,
198+ validator: @escaping TokenSubscriberValidator ,
199+ responseProvider: URLResponseProvider
200+ ) async throws -> Login {
201+ guard let tokenURL = URL ( string: authorizationServer. tokenEndpoint) else {
202+ throw AuthenticatorError . missingTokenURL
203+ }
216204
217- throw AuthenticatorError . refreshNotPossible
205+ var request = URLRequest ( url: tokenURL)
206+
207+ request. httpMethod = " POST "
208+ request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
209+ request. setValue ( " application/json " , forHTTPHeaderField: " Accept " )
210+ request. httpBody = try JSONEncoder ( ) . encode ( tokenRequest)
211+
212+ let ( data, response) = try await responseProvider ( request)
213+
214+ guard
215+ let httpResponse = response as? HTTPURLResponse ,
216+ httpResponse. statusCode >= 200 && httpResponse. statusCode < 300
217+ else {
218+ if let error = try ? JSONDecoder ( ) . decode ( OAuthErrorResponse . self, from: data) {
219+ switch error. error {
220+ case " invalid_request " :
221+ throw AuthenticatorError . invalidRequest ( error. error, error. errorDescription ?? " " )
222+ case " invalid_grant " :
223+ throw AuthenticatorError . invalidGrant ( error. error, error. errorDescription ?? " " )
224+ default :
225+ throw AuthenticatorError . unrecognizedError ( error. error, error. errorDescription ?? " " )
226+ }
218227 }
219228
220- let tokenResponse = try JSONDecoder ( ) . decode ( TokenResponse . self, from: data)
229+ throw AuthenticatorError . unrecognizedError (
230+ " unknown_response " , " Received an unexpected response from the authorization server " )
231+ }
221232
222- guard tokenResponse. token_type == " DPoP " else {
223- throw AuthenticatorError . dpopTokenExpected ( tokenResponse . token_type )
224- }
233+ guard let tokenResponse = try ? JSONDecoder ( ) . decode ( TokenResponse . self , from : data ) else {
234+ throw AuthenticatorError . unrecognizedError ( " invalid_json " , " Decoding response JSON " )
235+ }
225236
226- if try await validator ( tokenResponse, server . issuer ) == false {
227- throw AuthenticatorError . tokenInvalid
228- }
237+ guard tokenResponse. token_type == " DPoP " else {
238+ throw AuthenticatorError . dpopTokenExpected ( tokenResponse . token_type )
239+ }
229240
230- return tokenResponse. login ( for: server. issuer)
241+ if try await validator ( tokenResponse, authorizationServer. issuer) == false {
242+ throw AuthenticatorError . tokenInvalid
231243 }
244+
245+ return tokenResponse. login ( for: authorizationServer. issuer)
232246 }
233247}
0 commit comments