···
+
import 'package:dio/dio.dart';
import 'package:http/http.dart' as http;
+
import '../dpop/fetch_dpop.dart';
import '../errors/token_invalid_error.dart';
import '../errors/token_revoked_error.dart';
import '../oauth/oauth_server_agent.dart';
···
/// The session getter for retrieving and refreshing tokens
final SessionGetterInterface sessionGetter;
+
/// Dio instance with DPoP interceptor for authenticated requests
/// Creates a new OAuth session.
···
/// - [server]: The OAuth server agent
/// - [sub]: The subject (user's DID)
/// - [sessionGetter]: The session getter for token management
required this.sessionGetter,
+
// Add DPoP interceptor for authenticated requests to resource servers
+
DpopFetchWrapperOptions(
+
nonces: server.dpopNonces,
+
sha256: server.runtime.sha256,
+
isAuthServer: false, // Resource server requests (PDS)
AtprotoDid get did => sub;
···
'Authorization': initialAuth,
+
// Make request with DPoP - the interceptor will automatically add DPoP header
+
final initialResponse = await _makeDpopRequest(
···
'Authorization': finalAuth,
+
final finalResponse = await _makeDpopRequest(
···
+
/// Makes an HTTP request with DPoP authentication.
+
/// Uses Dio with DPoP interceptor which automatically adds:
+
/// - DPoP header with proof JWT
+
/// - Access token hash (ath) binding
+
/// Throws [DioException] for network errors, timeouts, and cancellations.
+
Future<http.Response> _makeDpopRequest(
Map<String, String>? headers,
+
// Make request with Dio - interceptor will add DPoP header
+
final response = await _dio.requestUri(
+
responseType: ResponseType.bytes, // Get raw bytes for compatibility
+
validateStatus: (status) =>
+
true, // Don't throw on any status code
+
// Convert Dio Response to http.Response for compatibility
+
return http.Response.bytes(
+
response.data as List<int>,
+
headers: response.headers.map.map(
+
(key, value) => MapEntry(key, value.join(', ')),
+
reasonPhrase: response.statusMessage,
+
} on DioException catch (e) {
+
// If we have a response (4xx/5xx), convert it to http.Response
+
if (e.response != null) {
+
final errorResponse = e.response!;
+
return http.Response.bytes(
+
errorResponse.data is List<int>
+
? errorResponse.data as List<int>
+
: (errorResponse.data?.toString() ?? '').codeUnits,
+
errorResponse.statusCode!,
+
headers: errorResponse.headers.map.map(
+
(key, value) => MapEntry(key, value.join(', ')),
+
reasonPhrase: errorResponse.statusMessage,
+
// Network errors, timeouts, cancellations - rethrow
/// Checks if a response indicates an invalid token.
···
/// Disposes of resources used by this session.