From ad7d423804a51b0993308867ef300c455183fb38 Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Fri, 20 Dec 2024 11:09:30 +1100 Subject: [PATCH] Add support for refreshing refresh tokens for Bluesky Fixes #23 --- Sources/OAuthenticator/Authenticator.swift | 2 +- Sources/OAuthenticator/Services/Bluesky.swift | 50 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Sources/OAuthenticator/Authenticator.swift b/Sources/OAuthenticator/Authenticator.swift index 96c19f6..06598d3 100644 --- a/Sources/OAuthenticator/Authenticator.swift +++ b/Sources/OAuthenticator/Authenticator.swift @@ -324,7 +324,7 @@ extension Authenticator { return nil } - let login = try await refreshProvider(login, config.appCredentials, { try await self.dpopResponse(for: $0, login: login) }) + let login = try await refreshProvider(login, config.appCredentials, { try await self.dpopResponse(for: $0, login: nil) }) try await storeLogin(login) diff --git a/Sources/OAuthenticator/Services/Bluesky.swift b/Sources/OAuthenticator/Services/Bluesky.swift index 7473562..499801e 100644 --- a/Sources/OAuthenticator/Services/Bluesky.swift +++ b/Sources/OAuthenticator/Services/Bluesky.swift @@ -18,6 +18,20 @@ public enum Bluesky { } } + struct RefreshTokenRequest: Hashable, Sendable, Codable { + public let refresh_token: String + public let redirect_uri: String + public let grant_type: String + public let client_id: String + + public init(refresh_token: String, redirect_uri: String, grant_type: String, client_id: String) { + self.refresh_token = refresh_token + self.redirect_uri = redirect_uri + self.grant_type = grant_type + self.client_id = client_id + } + } + struct TokenResponse: Hashable, Sendable, Codable { public let access_token: String public let refresh_token: String? @@ -124,6 +138,40 @@ public enum Bluesky { } private static func refreshProvider(server: ServerMetadata) -> TokenHandling.RefreshProvider { - { _, _, _ in throw AuthenticatorError.refreshUnsupported } + { login, credentials, responseProvider -> Login in + guard let refreshToken = login.refreshToken?.value else { + throw AuthenticatorError.missingAuthorizationCode + } + + guard let tokenURL = URL(string: server.tokenEndpoint) else { + throw AuthenticatorError.missingTokenURL + } + + let tokenRequest = RefreshTokenRequest( + refresh_token: refreshToken, + redirect_uri: credentials.callbackURL.absoluteString, + grant_type: "refresh_token", + client_id: credentials.clientId // is this field truly necessary? + ) + + var request = URLRequest(url: tokenURL) + + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try JSONEncoder().encode(tokenRequest) + + let (data, response) = try await responseProvider(request) + + print("data:", String(decoding: data, as: UTF8.self)) + print("response:", response) + + let tokenResponse = try JSONDecoder().decode(TokenResponse.self, from: data) + + guard tokenResponse.token_type == "DPoP" else { + throw AuthenticatorError.dpopTokenExpected(tokenResponse.token_type) + } + + return tokenResponse.login(for: server.issuer) + } } }