···11+//
22+// Exports.swift
33+// tcatproto
44+//
55+// Created by Woodrow Melling on 4/14/26.
66+//
77+88+@_exported import ATProto
+6-1
Sources/ATProtoOAuth/OAuthClient.swift
···135135 @Dependency(\.authServerClient) var authServerClient
136136 let metadata = try await authServerClient.fetchMetadata(pdsHost)
137137138138+ guard metadata.issuer.description == "https://\(pdsHost.rawValue)" else {
139139+ throw AuthorizationError.issuerMismatch
140140+ }
141141+138142 let pkceChallenge = try OAuth.PKCE.Challenge()
139143140144 @Dependency(\.uuid) var uuid
···236240 _ appID: OAuth.App.ID
237241 ) async throws -> OAuth.Token.Response {
238242239239- let request = OAuth.Token.RefreshRequest(refreshToken: session.refreshToken, appID: appID)
243243+ guard let refreshToken = session.refreshToken else { throw AuthorizationError.noRefreshToken }
244244+ let request = OAuth.Token.RefreshRequest(refreshToken: refreshToken, appID: appID)
240245241246242247 @Dependency(\.httpClient) var httpClient
+24-3
Sources/ATProtoOAuth/Session.swift
···1616 public let tokenEndpoint: OAuth.AuthorizationServer.TokenEndpoint
1717 public let scope: OAuth.Scope
1818 public let accessToken: OAuth.AccessToken
1919- public let refreshToken: OAuth.RefreshToken
1919+ public let refreshToken: OAuth.RefreshToken?
2020 public let dpopKey: OAuth.DPOP.Key
21212222 public struct Pending: Sendable {
···5252 }
5353}
54545555+/// Authenticates the given operation using the current `defaultSession` dependency.
5656+///
5757+/// Adds DPoP proof and `Authorization: DPoP <token>` headers to all HTTP requests
5858+/// made within the operation.
5959+public func withDefaultSessionAuthentication<T>(
6060+ operation: () async throws -> T
6161+) async throws -> T {
6262+ @Dependency(\.defaultSession) var session
6363+// guard let session
6464+// else {
6565+// reportIssue("No session created")
6666+// throw NoSessionError()
6767+// }
6868+ return try await session.withAuthentication(operation: operation)
6969+}
7070+7171+struct NoSessionError: Error {}
5572// Inject the current authenticated session into the dependency graph so that
5673// any feature running inside an authenticated scope can read it without having
5774// to thread it through state manually.
···6279 }
6380}
64816565-private enum DefaultSessionKey: TestDependencyKey {
6666- static let testValue = OAuth.Session(
8282+public enum DefaultSessionKey: TestDependencyKey {
8383+ public static let testValue: OAuth.Session = .placeholder
8484+}
8585+8686+extension OAuth.Session {
8787+ public static let placeholder = OAuth.Session(
6788 accountID: .init(DID("did:plc:placeholder")!),
6889 tokenEndpoint: .init(URL(string: "https://placeholder.example/oauth/token")!),
6990 scope: "atproto",
+1-1
Sources/ATProtoOAuth/Token.swift
···45454646 public struct Response: Sendable, Equatable, Decodable {
4747 public let accessToken: OAuth.AccessToken
4848- public let refreshToken: OAuth.RefreshToken
4848+ public let refreshToken: OAuth.RefreshToken?
4949 public let tokenType: OAuth.Token.Kind
5050 public let expiresIn: TimeInterval
5151 public let scope: OAuth.Scope
-114
Sources/ATProtoSession/ATProtoSession.swift
···11-//
22-// ATProtoSession.swift
33-// tcatproto
44-//
55-// Created by Woodrow Melling on 4/7/26.
66-//
77-88-import ATProto
99-import XRPCClient
1010-import Tagged
1111-import TaggedTypesMacro
1212-1313-extension ATProto {
1414- // MARK: Server
1515- enum Server {
1616- struct CreateSession: XRPC.Request {
1717- static let nsid: ATProto.NSID = "com.atproto.server.createSession"
1818- static let type: XRPC.RequestType = .procedure
1919-2020-2121- var input: Input
2222-2323-2424- public init(input: Input) {
2525- self.input = input
2626- }
2727-2828- struct Input: Encodable {
2929- let identifier: ID
3030- let password: Atmosphere.Account.Password
3131- let authFactorToken: String
3232- let allowTakedown = false
3333- }
3434-3535- struct Output: Decodable {
3636- let accessJwt: ATProto.LegacySession.AccessToken
3737- let refreshJwt: ATProto.LegacySession.RefreshToken
3838- let handle: Atmosphere.Account.Handle
3939- let did: DID
4040-4141- let didDoc: DID.Document?
4242-4343- let email: Atmosphere.Account.Email?
4444- let emailConfirmed: Bool?
4545- let emailAuthFactor: Bool?
4646-4747- let active: Bool
4848-4949- enum Status: String, Decodable {
5050- case takendown, suspended, deactivated
5151- }
5252- }
5353- }
5454- }
5555-}
5656-5757-import DependenciesMacros
5858-import Dependencies
5959-6060-extension ATProto.Server {
6161- @DependencyClient
6262- struct Client: Sendable {
6363- var createSession: @Sendable (CreateSession.Input) async throws -> CreateSession.Output
6464- }
6565-}
6666-6767-extension ATProto.Server.Client: DependencyKey {
6868- static let testValue: ATProto.Server.Client = Self()
6969- static let liveValue: ATProto.Server.Client = .init(
7070- createSession: {
7171- @Dependency(XRPC.Client.self) var xrpcCient
7272- let request = ATProto.Server.CreateSession(input: $0)
7373- return try await xrpcCient.run(request)
7474- }
7575- )
7676-}
7777-7878-extension ATProto.Server.CreateSession {
7979- enum ID: Encodable {
8080- case handle(Atmosphere.Account.Handle)
8181- case did(DID)
8282- case email(Atmosphere.Account.Email)
8383-8484- func encode(to encoder: any Encoder) throws {
8585- var container = encoder.singleValueContainer()
8686- switch self {
8787- case .handle(let h): try container.encode(h.rawValue)
8888- case .did(let d): try container.encode(d.description)
8989- case .email(let e): try container.encode(e)
9090- }
9191- }
9292- }
9393-}
9494-9595-extension Atmosphere.Account {
9696- @TaggedTypes public enum _Tags {
9797- @Tagged(String.self) public enum Email {}
9898- @Tagged(String.self) public enum Password {}
9999- }
100100-}
101101-102102-extension ATProto {
103103- struct LegacySession {
104104- @TaggedTypes public enum _Tags {
105105- @Tagged(String.self) public enum AccessToken {}
106106- @Tagged(String.self) public enum RefreshToken {}
107107- }
108108-109109- let did: DID
110110- }
111111-}
112112-113113-114114-