V0.12.x (#215)
* fix #203 NetworkTransport race, with main-isolated variables (#206) Co-authored-by: wti <wti@users.noreply.github.com> * feat/auth: Authentication implementation (#205) * feat: added authentication * feat(auth): improvement to error handling * feat(auth): updated errors and tests in OAuthClientRegistrar * feat(auth): updated ci conformance package version * feat(auth): bumped node version * feat(auth): updated ci conformance step * feat(auth): improvement to Linux support * chore: added docc plugin * chore: added spi yml * chore: added documentation step to ci * chore: increased timeout for test from 5 to 10m --------- Co-authored-by: Paal Øye-Strømme <paal.o.eye@gmail.com> Co-authored-by: wti <wti@users.noreply.github.com> Co-authored-by: yehorsobko <yehorsobko@macpaw.com>
This commit is contained in:
parent
973b46b8f7
commit
6132fd4b5b
1 changed files with 10925 additions and 199 deletions
|
|
@ -0,0 +1,90 @@
|
|||
import Foundation
|
||||
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
/// Internal protocol for making OAuth token endpoint requests.
|
||||
protocol OAuthTokenRequesting: Sendable {
|
||||
func request(
|
||||
parameters: inout [String: String],
|
||||
endpoint: URL,
|
||||
authentication: OAuthConfiguration.TokenEndpointAuthentication,
|
||||
session: URLSession
|
||||
) async throws -> OAuthTokenResponse
|
||||
}
|
||||
|
||||
/// Stateless OAuth token endpoint HTTP client.
|
||||
///
|
||||
/// Handles the low-level HTTP mechanics of making token requests.
|
||||
struct OAuthTokenEndpointClient: Sendable {
|
||||
let urlValidator: OAuthURLValidator
|
||||
|
||||
init(urlValidator: OAuthURLValidator) {
|
||||
self.urlValidator = urlValidator
|
||||
}
|
||||
|
||||
/// Makes a token request to the given endpoint.
|
||||
func request(
|
||||
parameters: inout [String: String],
|
||||
endpoint: URL,
|
||||
authentication: OAuthConfiguration.TokenEndpointAuthentication,
|
||||
session: URLSession
|
||||
) async throws -> OAuthTokenResponse {
|
||||
var urlRequest = URLRequest(url: endpoint)
|
||||
urlRequest.httpMethod = "POST"
|
||||
urlRequest.setValue(ContentType.formURLEncoded, forHTTPHeaderField: HTTPHeaderName.contentType)
|
||||
urlRequest.setValue(ContentType.json, forHTTPHeaderField: HTTPHeaderName.accept)
|
||||
|
||||
try await authentication.apply(
|
||||
to: &urlRequest,
|
||||
bodyParameters: ¶meters,
|
||||
tokenEndpoint: endpoint
|
||||
)
|
||||
urlRequest.httpBody = encodeForm(parameters)
|
||||
|
||||
let (data, response) = try await session.data(for: urlRequest)
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw OAuthAuthorizationError.tokenRequestFailed(statusCode: -1, oauthError: nil)
|
||||
}
|
||||
|
||||
guard (200..<300).contains(httpResponse.statusCode) else {
|
||||
let oauthError =
|
||||
(try? JSONDecoder().decode(OAuthTokenErrorResponse.self, from: data))?.error
|
||||
throw OAuthAuthorizationError.tokenRequestFailed(
|
||||
statusCode: httpResponse.statusCode,
|
||||
oauthError: oauthError
|
||||
)
|
||||
}
|
||||
|
||||
let decoded = try JSONDecoder().decode(OAuthTokenResponse.self, from: data)
|
||||
guard !decoded.accessToken.isEmpty else {
|
||||
throw OAuthAuthorizationError.tokenResponseInvalid
|
||||
}
|
||||
let tokenType = decoded.tokenType.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !tokenType.isEmpty,
|
||||
tokenType.caseInsensitiveCompare(OAuthTokenType.bearer) == .orderedSame
|
||||
else {
|
||||
throw OAuthAuthorizationError.tokenResponseInvalid
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
private func encodeForm(_ params: [String: String]) -> Data {
|
||||
let body = params
|
||||
.sorted { $0.key < $1.key }
|
||||
.map { key, value in "\(percentEncode(key))=\(percentEncode(value))" }
|
||||
.joined(separator: "&")
|
||||
return Data(body.utf8)
|
||||
}
|
||||
|
||||
private func percentEncode(_ string: String) -> String {
|
||||
let allowed = CharacterSet(
|
||||
charactersIn:
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
||||
)
|
||||
return string.addingPercentEncoding(withAllowedCharacters: allowed) ?? string
|
||||
}
|
||||
}
|
||||
|
||||
extension OAuthTokenEndpointClient: OAuthTokenRequesting {}
|
||||
Loading…
Reference in a new issue