diff --git a/lib/src/core/auth/data/repositories/auth_repository_impl.dart b/lib/src/core/auth/data/repositories/auth_repository_impl.dart index b20ab67c..e1f80fb8 100644 --- a/lib/src/core/auth/data/repositories/auth_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/auth_repository_impl.dart @@ -8,6 +8,7 @@ import 'package:http/http.dart' as http; import 'package:spark/src/core/auth/data/models/login_result.dart'; import 'package:spark/src/core/auth/data/repositories/auth_repository.dart'; import 'package:spark/src/core/storage/storage.dart'; +import 'package:spark/src/core/utils/did_utils.dart'; import 'package:spark/src/core/utils/logging/log_service.dart'; import 'package:spark/src/core/utils/logging/logger.dart'; @@ -50,6 +51,20 @@ class AuthRepositoryImpl implements AuthRepository { } } + /// Fetches a DID document, handling both did:plc and did:web methods. + Future> _fetchDidDocument(String did) async { + final url = DidUtils.buildDidDocumentUrl(did); + _logger.d('Fetching DID document from: $url'); + final response = await http.get(url); + + if (response.statusCode != 200) { + _logger.e('Failed to fetch DID document: ${response.statusCode}'); + throw Exception('Failed to fetch DID document: ${response.statusCode}'); + } + + return json.decode(response.body) as Map; + } + String? _extractPdsDomain(Map doc) { final services = doc['service'] as List?; if (services == null || services.isEmpty) { @@ -121,14 +136,12 @@ class AuthRepositoryImpl implements AuthRepository { : null; if (service == null) { - _logger.d('Fetching DID document from PLC directory'); - final didDocResponse = await http.get( - Uri.parse('https://plc.directory/${_session!.did}'), - ); - if (didDocResponse.statusCode == 200) { - service = _extractPdsDomain( - json.decode(didDocResponse.body) as Map, - ); + _logger.d('Fetching DID document'); + try { + final didDoc = await _fetchDidDocument(_session!.did); + service = _extractPdsDomain(didDoc); + } catch (e) { + _logger.e('Failed to fetch DID document during refresh', error: e); } } @@ -202,18 +215,7 @@ class AuthRepositoryImpl implements AuthRepository { _logger ..d('Resolved DID: $did') ..d('Fetching DID document'); - final didDocResponse = await http.get( - Uri.parse('https://plc.directory/$did'), - ); - - if (didDocResponse.statusCode != 200) { - _logger.e('Failed to fetch DID document: ${didDocResponse.statusCode}'); - throw Exception( - 'Failed to fetch DID document: ${didDocResponse.statusCode}', - ); - } - - final didDoc = json.decode(didDocResponse.body); + final didDoc = await _fetchDidDocument(did); final pdsUrl = (didDoc['service'] as List).firstWhere( diff --git a/lib/src/core/auth/data/repositories/identity_repository_impl.dart b/lib/src/core/auth/data/repositories/identity_repository_impl.dart index 2f9916a7..78a6c4bb 100644 --- a/lib/src/core/auth/data/repositories/identity_repository_impl.dart +++ b/lib/src/core/auth/data/repositories/identity_repository_impl.dart @@ -7,6 +7,7 @@ import 'package:http/http.dart' as http; import 'package:spark/src/core/auth/data/repositories/identity_repository.dart'; import 'package:spark/src/core/storage/storage.dart'; +import 'package:spark/src/core/utils/did_utils.dart'; import 'package:spark/src/core/utils/logging/log_service.dart'; import 'package:spark/src/core/utils/logging/logger.dart'; @@ -198,14 +199,12 @@ class IdentityRepositoryImpl implements IdentityRepository { } Future?> _fetchAndCacheDidDoc(String did) async { - final url = Uri.parse('https://plc.directory/$did'); + final url = DidUtils.buildDidDocumentUrl(did); final response = await http.get(url); if (response.statusCode != 200) { - _logger.w( - 'Failed to resolve DID document. Status: ${response.statusCode}', - ); - return null; + _logger.e('Failed to fetch DID document: ${response.statusCode}'); + throw Exception('Failed to fetch DID document: ${response.statusCode}'); } final didDoc = json.decode(response.body); diff --git a/lib/src/core/utils/did_utils.dart b/lib/src/core/utils/did_utils.dart new file mode 100644 index 00000000..2ca24afd --- /dev/null +++ b/lib/src/core/utils/did_utils.dart @@ -0,0 +1,67 @@ +/// Utility class for working with Decentralized Identifiers (DIDs). +/// +/// Provides methods for building DID document URLs for various DID methods +/// including did:plc and did:web. +class DidUtils { + DidUtils._(); + + /// Builds the URL for fetching a DID document based on the DID method. + /// + /// Supported DID methods: + /// - `did:plc` -> `https://plc.directory/` + /// - `did:web` -> `https:///.well-known/did.json` (or path-based) + /// + /// Throws [ArgumentError] if the DID is malformed (e.g., empty domain). + /// + /// Examples: + /// ```dart + /// // did:plc + /// DidUtils.buildDidDocumentUrl('did:plc:abc123'); + /// // Returns: https://plc.directory/did:plc:abc123 + /// + /// // did:web (domain only) + /// DidUtils.buildDidDocumentUrl('did:web:example.com'); + /// // Returns: https://example.com/.well-known/did.json + /// + /// // did:web (with path) + /// DidUtils.buildDidDocumentUrl('did:web:example.com:user:alice'); + /// // Returns: https://example.com/user/alice/did.json + /// ``` + static Uri buildDidDocumentUrl(String did) { + if (did.startsWith('did:web:')) { + return _buildDidWebUrl(did); + } + // Default: did:plc or other methods use PLC directory + return Uri.parse('https://plc.directory/$did'); + } + + /// Builds a URL for a did:web identifier. + static Uri _buildDidWebUrl(String did) { + // did:web: or did:web::: + final webPart = did.substring('did:web:'.length); + + if (webPart.isEmpty) { + throw ArgumentError('Invalid did:web identifier: missing domain'); + } + + // Split by ':' first, then decode each segment + // This correctly handles percent-encoded colons in the domain (e.g., ports) + final segments = webPart.split(':').map(Uri.decodeComponent).toList(); + + if (segments.isEmpty || segments.first.isEmpty) { + throw ArgumentError('Invalid did:web identifier: missing domain'); + } + + final domain = segments.first; + + if (segments.length == 1) { + // did:web:example.com -> https://example.com/.well-known/did.json + return Uri.parse('https://$domain/.well-known/did.json'); + } else { + // did:web:example.com:path:to:resource -> + // https://example.com/path/to/resource/did.json + final path = segments.skip(1).join('/'); + return Uri.parse('https://$domain/$path/did.json'); + } + } +} diff --git a/lib/src/core/utils/utils.dart b/lib/src/core/utils/utils.dart index 306e6210..935a8ac8 100644 --- a/lib/src/core/utils/utils.dart +++ b/lib/src/core/utils/utils.dart @@ -1,3 +1,4 @@ +export 'did_utils.dart'; export 'json_utils.dart'; export 'logging/logging.dart'; export 'text_formatter.dart';