this repo has no description

first pass at using libraries to implement oauth

+94 -1
+9 -1
Package.swift
··· 17 17 targets: ["CoreATProtocol"] 18 18 ), 19 19 ], 20 + dependencies: [ 21 + .package(url: "https://github.com/ChimeHQ/OAuthenticator", branch: "main"), 22 + .package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0"), 23 + ], 20 24 targets: [ 21 25 .target( 22 - name: "CoreATProtocol" 26 + name: "CoreATProtocol", 27 + dependencies: [ 28 + "OAuthenticator", 29 + .product(name: "JWTKit", package: "jwt-kit"), 30 + ], 23 31 ), 24 32 .testTarget( 25 33 name: "CoreATProtocolTests",
+85
Sources/CoreATProtocol/LoginService.swift
··· 1 + // 2 + // LoginService.swift 3 + // CoreATProtocol 4 + // 5 + // Created by Thomas Rademaker on 10/17/25. 6 + // 7 + 8 + import Foundation 9 + import OAuthenticator 10 + import JWTKit 11 + import CryptoKit 12 + 13 + @APActor 14 + class LoginService { 15 + private var keys: JWTKeyCollection 16 + private var privateKey: ES256PrivateKey 17 + 18 + public init() async { 19 + // Create keys once during initialization 20 + self.privateKey = ES256PrivateKey() 21 + self.keys = JWTKeyCollection() 22 + // Add the key to the collection 23 + await self.keys.add(ecdsa: privateKey) 24 + } 25 + 26 + public func login(account: String, clientMetadataEndpoint: String) async throws { 27 + let provider = URLSession.defaultProvider 28 + let host = APEnvironment.current.host ?? "" 29 + let server = if host.hasPrefix("https://") { 30 + String(host.dropFirst(8)) 31 + } else if host.hasPrefix("http://") { 32 + String(host.dropFirst(7)) 33 + } else { host } 34 + 35 + let clientConfig = try await ClientMetadata.load(for: clientMetadataEndpoint, provider: provider) 36 + let serverConfig = try await ServerMetadata.load(for: server, provider: provider) 37 + 38 + // Create storage for persisting login state 39 + let loginStorage = LoginStorage { 40 + // Implement retrieving stored login 41 + // Return stored Login if it exists, or nil 42 + return nil 43 + } storeLogin: { login in 44 + // Implement storing the login 45 + // Store the login securely 46 + 47 + print("LOGIN: \(login)") 48 + } 49 + 50 + let jwtGenerator: DPoPSigner.JWTGenerator = { params in 51 + try await self.generateJWT(params: params) 52 + } 53 + 54 + let tokenHandling = Bluesky.tokenHandling(account: account, server: serverConfig, jwtGenerator: jwtGenerator) 55 + let config = Authenticator.Configuration(appCredentials: clientConfig.credentials, loginStorage: loginStorage, tokenHandling: tokenHandling, mode: .automatic) 56 + let authenticator = Authenticator(config: config) 57 + try await authenticator.authenticate() 58 + } 59 + 60 + private func generateJWT(params: DPoPSigner.JWTParameters) async throws -> String { 61 + // Create DPoP payload using existing keys 62 + let payload = DPoPPayload( 63 + htm: params.httpMethod, 64 + htu: params.requestEndpoint, 65 + iat: .init(value: .now), 66 + jti: .init(value: UUID().uuidString), 67 + nonce: params.nonce 68 + ) 69 + 70 + // Sign with existing keys 71 + return try await self.keys.sign(payload) 72 + } 73 + } 74 + 75 + private struct DPoPPayload: JWTPayload { 76 + let htm: String 77 + let htu: String 78 + let iat: IssuedAtClaim 79 + let jti: IDClaim 80 + let nonce: String? 81 + 82 + func verify(using key: some JWTAlgorithm) throws { 83 + // No additional verification needed 84 + } 85 + }