From 551e1d01eae74d42b33103d6d924c5c2469bfd7e Mon Sep 17 00:00:00 2001 From: systemblue Date: Thu, 4 Jun 2026 11:15:25 +0200 Subject: [PATCH] feat: add .bearer credential for OAuth access tokens (#4) --- .../ForgejoKit/Services/ForgejoClient.swift | 28 +++++++++++++++++-- .../BearerCredentialTests.swift | 27 ++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Tests/ForgejoKitTests/BearerCredentialTests.swift diff --git a/Sources/ForgejoKit/Services/ForgejoClient.swift b/Sources/ForgejoKit/Services/ForgejoClient.swift index d3b345a..f4a3064 100644 --- a/Sources/ForgejoKit/Services/ForgejoClient.swift +++ b/Sources/ForgejoKit/Services/ForgejoClient.swift @@ -7,6 +7,7 @@ public final class ForgejoClient: Sendable { private enum AuthCredential: Sendable { case basic(base64: String) case token(String) + case bearer(String) } private let credential: AuthCredential @@ -48,6 +49,21 @@ public final class ForgejoClient: Sendable { ) } + /// Creates a client authenticated with an OAuth2 access token. + /// + /// OAuth2 access tokens are sent with the `Authorization: Bearer` scheme, + /// unlike personal access tokens which use `Authorization: token`. + public convenience init( + serverURL: String, username: String, bearerToken: String, + allowSelfSignedCertificates: Bool = false, + ) { + self.init( + serverURL: serverURL, username: username, + credential: .bearer(bearerToken), + allowSelfSignedCertificates: allowSelfSignedCertificates, + ) + } + public static func login( serverURL: String, username: String, password: String, allowSelfSignedCertificates: Bool = false, @@ -125,11 +141,19 @@ public final class ForgejoClient: Sendable { } private func applyAuthHeader(to request: inout URLRequest) { + request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization") + } + + /// The `Authorization` header value for this client's credential. + /// Exposed internally so tests can assert the scheme without issuing a request. + var authorizationHeaderValue: String { switch credential { case let .basic(base64): - request.setValue("Basic \(base64)", forHTTPHeaderField: "Authorization") + "Basic \(base64)" case let .token(token): - request.setValue("token \(token)", forHTTPHeaderField: "Authorization") + "token \(token)" + case let .bearer(token): + "Bearer \(token)" } } diff --git a/Tests/ForgejoKitTests/BearerCredentialTests.swift b/Tests/ForgejoKitTests/BearerCredentialTests.swift new file mode 100644 index 0000000..722190b --- /dev/null +++ b/Tests/ForgejoKitTests/BearerCredentialTests.swift @@ -0,0 +1,27 @@ +import Foundation +import Testing +@testable import ForgejoKit + +struct BearerCredentialTests { + @Test func bearerCredentialUsesBearerScheme() { + let client = ForgejoClient( + serverURL: "https://codeberg.org", username: "octocat", bearerToken: "abc123", + ) + #expect(client.authorizationHeaderValue == "Bearer abc123") + } + + @Test func tokenCredentialStillUsesTokenScheme() { + let client = ForgejoClient( + serverURL: "https://codeberg.org", username: "octocat", token: "abc123", + ) + #expect(client.authorizationHeaderValue == "token abc123") + } + + @Test func basicCredentialUsesBasicScheme() { + let client = ForgejoClient( + serverURL: "https://codeberg.org", username: "octocat", password: "pw", + ) + let expected = "Basic " + Data("octocat:pw".utf8).base64EncodedString() + #expect(client.authorizationHeaderValue == expected) + } +}