this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

strengthen auth:

+30 -4
+30 -4
Sources/CoreATProtocol/OAuth/ATProtoOAuth.swift
··· 23 23 public let pdsEndpoint: String 24 24 } 25 25 26 + /// Client authentication method declared in the client metadata's 27 + /// `token_endpoint_auth_method`. Determines whether OAuth flows may fall back 28 + /// to unproxied requests when an auth proxy is temporarily unreachable. 29 + public enum ATProtoClientAuthMethod: Sendable { 30 + /// Public client. Direct requests to the authorization server are valid; 31 + /// the auth proxy (if configured) is an optional signing helper. 32 + case none 33 + /// Confidential client using `private_key_jwt`. The auth server requires a 34 + /// `client_assertion` JWT on every token request, which only the auth proxy 35 + /// can produce — direct fallback would be rejected and risks consuming the 36 + /// single-use refresh token, so it is disabled. 37 + case privateKeyJWT 38 + } 39 + 26 40 /// Configuration for AT Protocol OAuth 27 41 public struct ATProtoOAuthConfig: Sendable { 28 42 public let clientMetadataURL: String 29 43 public let redirectURI: String 30 44 public let scopes: [String] 31 45 public let authProxyBaseURL: String? 46 + public let clientAuthMethod: ATProtoClientAuthMethod 32 47 33 48 public init( 34 49 clientMetadataURL: String, 35 50 redirectURI: String, 36 51 scopes: [String] = ["atproto", "transition:generic"], 37 - authProxyBaseURL: String? = nil 52 + authProxyBaseURL: String? = nil, 53 + clientAuthMethod: ATProtoClientAuthMethod = .none 38 54 ) { 39 55 self.clientMetadataURL = clientMetadataURL 40 56 self.redirectURI = redirectURI 41 57 self.scopes = scopes 42 58 self.authProxyBaseURL = authProxyBaseURL 59 + self.clientAuthMethod = clientAuthMethod 43 60 } 44 61 } 45 62 ··· 470 487 refreshedLogin = try await refreshProvider(login, appCredentials, proxyResponseProvider) 471 488 usedAuthProxy = true 472 489 } catch { 473 - guard shouldRetryWithoutAuthProxy(after: error) else { 490 + // A confidential client cannot authenticate directly — the auth 491 + // server rejects refreshes without a `client_assertion`, and a 492 + // rejected refresh may still consume the single-use token. 493 + // Surface the proxy error so the caller can retry later. 494 + guard config.clientAuthMethod != .privateKeyJWT, 495 + shouldRetryWithoutAuthProxy(after: error) else { 474 496 throw error 475 497 } 476 498 ··· 601 623 usedAuthProxy: prefersAuthProxy 602 624 ) 603 625 } catch { 604 - if let fallbackAuthenticator, shouldRetryWithoutAuthProxy(after: error) { 626 + if let fallbackAuthenticator, 627 + config.clientAuthMethod != .privateKeyJWT, 628 + shouldRetryWithoutAuthProxy(after: error) { 605 629 return AuthenticationAttemptResult( 606 630 login: try await fallbackAuthenticator.authenticate(), 607 631 usedAuthProxy: false ··· 612 636 } 613 637 } 614 638 615 - if let fallbackAuthenticator, shouldRetryWithoutAuthProxy(after: error) { 639 + if let fallbackAuthenticator, 640 + config.clientAuthMethod != .privateKeyJWT, 641 + shouldRetryWithoutAuthProxy(after: error) { 616 642 return AuthenticationAttemptResult( 617 643 login: try await fallbackAuthenticator.authenticate(), 618 644 usedAuthProxy: false