diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3bea01fc9..9ef5f3487 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -169,7 +169,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a33..a6b826db2 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ getHost({ } } } + +Future<(String?, String)> getIssuerAndPreAuthorizedCode({ + required OIDC4VCType oidc4vcType, + required String scannedResponse, + required DioClient dioClient, +}) async { + String? preAuthorizedCode; + late String issuer; + + final Uri uriFromScannedResponse = Uri.parse(scannedResponse); + + switch (oidc4vcType) { + case OIDC4VCType.DEFAULT: + case OIDC4VCType.HEDERA: + case OIDC4VCType.EBSIV3: + final dynamic credentialOfferJson = await getCredentialOfferJson( + scannedResponse: scannedResponse, + dioClient: dioClient, + ); + if (credentialOfferJson == null) throw Exception(); + + preAuthorizedCode = credentialOfferJson['grants'] + ['urn:ietf:params:oauth:grant-type:pre-authorized_code'] + ['pre-authorized_code'] + .toString(); + issuer = credentialOfferJson['credential_issuer'].toString(); + + case OIDC4VCType.GAIAX: + case OIDC4VCType.EBSIV2: + issuer = uriFromScannedResponse.queryParameters['issuer'].toString(); + preAuthorizedCode = + uriFromScannedResponse.queryParameters['pre-authorized_code']; + + case OIDC4VCType.JWTVC: + throw Exception(); + } + + return (preAuthorizedCode, issuer); +} diff --git a/lib/app/shared/message_handler/global_message.dart b/lib/app/shared/message_handler/global_message.dart index 1ea2c9f2d..459df0984 100644 --- a/lib/app/shared/message_handler/global_message.dart +++ b/lib/app/shared/message_handler/global_message.dart @@ -84,6 +84,8 @@ class GlobalMessage { String get NETWORK_ERROR_NOT_FOUND => l10n.networkErrorNotFound; + String get NETWORK_ERROR_NOT_READY => ''; + String get RESPONSE_STRING_FAILED_TO_LOAD_PROFILE => l10n.failedToLoadProfile; String get RESPONSE_STRING_FAILED_TO_DO_OPERATION => l10n.failedToDoOperation; @@ -379,4 +381,8 @@ class GlobalMessage { String RESPONSE_STRING_youcanSelectOnlyXCredential(String value) => l10n.youcanSelectOnlyXCredential(value); + String get RESPONSE_STRING_theCredentialIsNotReady => + l10n.theCredentialIsNotReady; + String get RESPONSE_STRING_theCredentialIsNoMoreReady => + l10n.theCredentialIsNoMoreReady; } diff --git a/lib/app/shared/message_handler/network_exception.dart b/lib/app/shared/message_handler/network_exception.dart index 3ebaa4279..f6e3eb4a5 100644 --- a/lib/app/shared/message_handler/network_exception.dart +++ b/lib/app/shared/message_handler/network_exception.dart @@ -44,6 +44,11 @@ class NetworkException with MessageHandler { message: NetworkError.NETWORK_ERROR_CONFLICT, data: error?.response?.data, ); + case 410: + return NetworkException( + message: NetworkError.NETWORK_ERROR_NOT_READY, + data: error?.response?.data, + ); case 412: return NetworkException( message: NetworkError.NETWORK_ERROR_PRECONDITION_FAILED, @@ -205,6 +210,8 @@ class NetworkException with MessageHandler { case NetworkError.NETWORK_ERROR_PRECONDITION_FAILED: return NetworkError.NETWORK_ERROR_PRECONDITION_FAILED .localise(context); + case NetworkError.NETWORK_ERROR_NOT_READY: + return NetworkError.NETWORK_ERROR_NOT_READY.localise(context); } } return ''; diff --git a/lib/app/shared/message_handler/response_message.dart b/lib/app/shared/message_handler/response_message.dart index ba6e0bb0e..56feaa96b 100644 --- a/lib/app/shared/message_handler/response_message.dart +++ b/lib/app/shared/message_handler/response_message.dart @@ -706,6 +706,18 @@ class ResponseMessage with MessageHandler { context, injectedMessage: injectedMessage, ); + + case ResponseString.RESPONSE_STRING_theCredentialIsNotReady: + return ResponseString.RESPONSE_STRING_theCredentialIsNotReady + .localise( + context, + ); + + case ResponseString.RESPONSE_STRING_theCredentialIsNoMoreReady: + return ResponseString.RESPONSE_STRING_theCredentialIsNoMoreReady + .localise( + context, + ); } } return ''; diff --git a/lib/credentials/cubit/credentials_cubit.dart b/lib/credentials/cubit/credentials_cubit.dart index f5f425a2b..e236cda65 100644 --- a/lib/credentials/cubit/credentials_cubit.dart +++ b/lib/credentials/cubit/credentials_cubit.dart @@ -131,13 +131,13 @@ class CredentialsCubit extends Cubit { } Future deleteById({ - required CredentialModel credential, + required String id, bool showMessage = true, }) async { emit(state.loading()); - await credentialsRepository.deleteById(credential.id); + await credentialsRepository.deleteById(id); final credentials = List.of(state.credentials) - ..removeWhere((element) => element.id == credential.id); + ..removeWhere((element) => element.id == id); final dummies = _getAvalaibleDummyCredentials(credentials); emit( state.copyWith( @@ -335,7 +335,7 @@ class CredentialsCubit extends Cubit { if (email == (iteratedCredentialSubjectModel as EmailPassModel).email) { await deleteById( - credential: storedCredential, + id: storedCredential.id, showMessage: false, ); break; @@ -363,7 +363,7 @@ class CredentialsCubit extends Cubit { storedCredential.credentialPreview.credentialSubjectModel; if (credentialSubjectModel.credentialSubjectType == card) { await deleteById( - credential: storedCredential, + id: storedCredential.id, showMessage: false, ); break; diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index a4fb4b68b..8f5d75b07 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -158,6 +158,14 @@ class CredentialDetailsCubit extends Cubit { } Future verifyProofOfPurpose(CredentialModel item) async { + if (item.data.isEmpty) { + return emit( + state.copyWith( + credentialStatus: CredentialStatus.pending, + status: AppStatus.idle, + ), + ); + } final vcStr = jsonEncode(item.data); final optStr = jsonEncode({'proofPurpose': 'assertionMethod'}); final result = await didKitProvider.verifyCredential(vcStr, optStr); diff --git a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart index 773f3db0f..6068ed640 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart @@ -115,7 +115,7 @@ class _CredentialsDetailsViewState extends State { if (confirm) { final credentialsCubit = context.read(); - await credentialsCubit.deleteById(credential: widget.credentialModel); + await credentialsCubit.deleteById(id: widget.credentialModel.id); } } @@ -258,18 +258,20 @@ class _CredentialsDetailsViewState extends State { credentialModel: widget.credentialModel, ), ], - CredentialField( - padding: const EdgeInsets.symmetric( - horizontal: 0, - vertical: 8, + if (widget.credentialModel.pendingInfo == null) ...[ + CredentialField( + padding: const EdgeInsets.symmetric( + horizontal: 0, + vertical: 8, + ), + title: l10n.format, + value: format, + titleColor: + Theme.of(context).colorScheme.titleColor, + valueColor: + Theme.of(context).colorScheme.valueColor, ), - title: l10n.format, - value: format, - titleColor: - Theme.of(context).colorScheme.titleColor, - valueColor: - Theme.of(context).colorScheme.valueColor, - ), + ], ], if (state.credentialDetailTabStatus == CredentialDetailTabStatus.activity) ...[ @@ -309,48 +311,65 @@ class _CredentialsDetailsViewState extends State { text: l10n.credentialDetailDeleteCard, ), const SizedBox(height: 8), - MyOutlinedButton( - text: widget.credentialModel.isLinkeInCard - ? l10n.exportToLinkedIn - : l10n.share, - onPressed: () { - if (widget.credentialModel.isLinkeInCard) { - Navigator.of(context).push( - GetLinkedinInfoPage.route( - credentialModel: widget.credentialModel, - ), - ); - } else { - if (widget.credentialModel.isEbsiCard) { - /// removing type that was added in add_ebsi_credential.dart - widget.credentialModel.data['credentialSubject'] - .remove('type'); - } - - late String data; - final String? jwt = widget.credentialModel.jwt; - if (jwt != null) { - data = jwt; + if (widget.credentialModel.pendingInfo == null) ...[ + MyOutlinedButton( + text: widget.credentialModel.isLinkeInCard + ? l10n.exportToLinkedIn + : l10n.share, + onPressed: () { + if (widget.credentialModel.isLinkeInCard) { + Navigator.of(context).push( + GetLinkedinInfoPage.route( + credentialModel: widget.credentialModel, + ), + ); } else { - data = jsonEncode(widget.credentialModel.data); - } + if (widget.credentialModel.isEbsiCard) { + /// removing type that was added in add_ebsi_credential.dart + widget + .credentialModel.data['credentialSubject'] + .remove('type'); + } - getLogger('CredentialDetailsPage - shared date') - .i(data); + late String data; + final String? jwt = widget.credentialModel.jwt; + if (jwt != null) { + data = jwt; + } else { + data = + jsonEncode(widget.credentialModel.data); + } - final box = - context.findRenderObject() as RenderBox?; - final subject = l10n.shareWith; + getLogger('CredentialDetailsPage - shared date') + .i(data); - Share.share( - data, - subject: subject, - sharePositionOrigin: - box!.localToGlobal(Offset.zero) & box.size, - ); - } - }, - ), + final box = + context.findRenderObject() as RenderBox?; + final subject = l10n.shareWith; + + Share.share( + data, + subject: subject, + sharePositionOrigin: + box!.localToGlobal(Offset.zero) & + box.size, + ); + } + }, + ), + ] else ...[ + MyOutlinedButton( + text: l10n.getItNow, + onPressed: () { + Navigator.of(context).pop(); + context + .read() + .startOIDC4VCDeferedCredentialIssuance( + credentialModel: widget.credentialModel, + ); + }, + ), + ], if (widget.credentialModel.shareLink != '') MyOutlinedButton.icon( icon: SvgPicture.asset( diff --git a/lib/dashboard/home/tab_bar/credentials/models/credential/credential.dart b/lib/dashboard/home/tab_bar/credentials/models/credential/credential.dart index 63368fdfb..1a9365ae1 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/credential/credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/credential/credential.dart @@ -44,7 +44,11 @@ class Credential { [ Proof.dummy(), ], - DefaultCredentialSubjectModel('dummy', 'dummy', const Author('')), + DefaultCredentialSubjectModel( + id: 'dummy7', + type: 'dummy8', + issuedBy: const Author(''), + ), [Translation('en', '')], [Translation('en', '')], CredentialStatusField.emptyCredentialStatusField(), diff --git a/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart b/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart index fd6898730..66db71482 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart @@ -28,6 +28,7 @@ class CredentialModel extends Equatable { this.domain, this.activities = const [], this.jwt, + this.pendingInfo, }); factory CredentialModel.fromJson(Map json) { @@ -91,6 +92,7 @@ class CredentialModel extends Equatable { final String? domain; final List activities; final String? jwt; + final PendingInfo? pendingInfo; Map toJson() => _$CredentialModelToJson(this); @@ -108,6 +110,7 @@ class CredentialModel extends Equatable { String? domain, List? activities, String? jwt, + PendingInfo? pendingInfo, }) { return CredentialModel( id: id ?? this.id, @@ -123,10 +126,11 @@ class CredentialModel extends Equatable { domain: domain ?? this.domain, activities: activities ?? this.activities, jwt: jwt ?? this.jwt, + pendingInfo: pendingInfo ?? this.pendingInfo, ); } - String get issuer => data['issuer'] as String; + String get issuer => data['issuer'] == null ? '' : data['issuer'] as String; static String fromJsonId(dynamic json) { if (json == null || json == '') { @@ -154,6 +158,9 @@ class CredentialModel extends Equatable { } Future getRevocationStatus() async { + if (data.isEmpty) { + return RevocationStatus.revoked; + } final String vcStr = jsonEncode(data); final String optStr = jsonEncode({ 'checks': ['credentialStatus'], @@ -230,6 +237,11 @@ class CredentialModel extends Equatable { display, expirationDate, credentialManifest, + receivedId, + challenge, + domain, activities, + jwt, + pendingInfo, ]; } diff --git a/lib/dashboard/home/tab_bar/credentials/models/default_credential_subject/default_credential_subject_model.dart b/lib/dashboard/home/tab_bar/credentials/models/default_credential_subject/default_credential_subject_model.dart index 68e6fcf80..f9e87ee0f 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/default_credential_subject/default_credential_subject_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/default_credential_subject/default_credential_subject_model.dart @@ -1,5 +1,4 @@ import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/home/tab_bar/credentials/models/author/author.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_subject/credential_subject_model.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -7,14 +6,13 @@ part 'default_credential_subject_model.g.dart'; @JsonSerializable(explicitToJson: true) class DefaultCredentialSubjectModel extends CredentialSubjectModel { - DefaultCredentialSubjectModel(String? id, String? type, Author? issuedBy) - : super( - id: id, - type: type, - issuedBy: issuedBy, - credentialSubjectType: CredentialSubjectType.defaultCredential, - credentialCategory: CredentialCategory.othersCards, - ); + DefaultCredentialSubjectModel({ + super.id, + super.type, + super.issuedBy, + super.credentialSubjectType = CredentialSubjectType.defaultCredential, + super.credentialCategory = CredentialCategory.othersCards, + }); factory DefaultCredentialSubjectModel.fromJson(Map json) => _$DefaultCredentialSubjectModelFromJson(json); diff --git a/lib/dashboard/home/tab_bar/credentials/models/model.dart b/lib/dashboard/home/tab_bar/credentials/models/model.dart index 238c53145..c0a8279d4 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/model.dart @@ -50,6 +50,7 @@ export 'over15/over15_model.dart'; export 'over18/over18_model.dart'; export 'passport_footprint/passport_footprint_model.dart'; export 'pcds_agent_certificate/pcds_agent_certificate_model.dart'; +export 'pending_info/pending_info.dart'; export 'phone_pass/phone_pass_model.dart'; export 'pigs_pass/pigs_pass_model.dart'; export 'polygon_associated_address/polygon_associated_address_credential.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/models/pending_info/pending_info.dart b/lib/dashboard/home/tab_bar/credentials/models/pending_info/pending_info.dart new file mode 100644 index 000000000..28169c7cf --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/pending_info/pending_info.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'pending_info.g.dart'; + +@JsonSerializable() +class PendingInfo extends Equatable { + const PendingInfo({ + required this.acceptanceToken, + required this.deferredCredentialEndpoint, + required this.format, + required this.url, + }); + + factory PendingInfo.fromJson(Map json) => + _$PendingInfoFromJson(json); + + final String acceptanceToken; + final String deferredCredentialEndpoint; + final String format; + final String url; + + Map toJson() => _$PendingInfoToJson(this); + + @override + List get props => [ + acceptanceToken, + deferredCredentialEndpoint, + format, + url, + ]; +} diff --git a/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart index 58b227478..07e7c0334 100644 --- a/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/oid4c4vc_pick/oid4c4vc_credential_pick/view/oid4c4vc_credential_pick_page.dart @@ -126,9 +126,9 @@ class Oidc4vcCredentialPickView extends StatelessWidget { '', [Proof.dummy()], DefaultCredentialSubjectModel( - 'dummy7', - 'dummy8', - const Author(''), + id: 'dummy7', + type: 'dummy8', + issuedBy: const Author(''), ), [Translation('en', '')], [Translation('en', '')], diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/dummy_credential_image.dart b/lib/dashboard/home/tab_bar/credentials/widgets/dummy_credential_image.dart index 27623a684..e7cbf1393 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/dummy_credential_image.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/dummy_credential_image.dart @@ -35,9 +35,9 @@ class DummyCredentialImage extends StatelessWidget { '', [Proof.dummy()], DefaultCredentialSubjectModel( - 'dummy7', - 'dummy8', - const Author(''), + id: 'dummy7', + type: 'dummy8', + issuedBy: const Author(''), ), [Translation('en', '')], [Translation('en', '')], diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index 483d47667..e3f0759da 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -7,8 +7,7 @@ import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/deep_link/deep_link.dart'; import 'package:altme/did/did.dart'; -import 'package:altme/oidc4vc/initiate_oidv4vc_credential_issuance.dart'; -import 'package:altme/oidc4vc/verify_encoded_data.dart'; +import 'package:altme/oidc4vc/oidc4vc.dart'; import 'package:altme/polygon_id/polygon_id.dart'; import 'package:altme/query_by_example/query_by_example.dart'; import 'package:altme/scan/scan.dart'; @@ -262,7 +261,6 @@ class QRCodeScanCubit extends Cubit { scannedResponse: state.uri.toString(), currentOIIDC4VCType: currentOIIDC4VCTypeForIssuance, qrCodeScanCubit: qrCodeScanCubit, - dioClient: dioClient, ); return; } @@ -498,7 +496,6 @@ class QRCodeScanCubit extends Cubit { required String scannedResponse, required OIDC4VCType currentOIIDC4VCType, required QRCodeScanCubit qrCodeScanCubit, - required DioClient dioClient, }) async { switch (currentOIIDC4VCType) { case OIDC4VCType.DEFAULT: @@ -506,7 +503,7 @@ class QRCodeScanCubit extends Cubit { case OIDC4VCType.EBSIV3: final dynamic credentialOfferJson = await getCredentialOfferJson( scannedResponse: scannedResponse, - dioClient: dioClient, + dioClient: client, ); if (credentialOfferJson == null) break; @@ -539,7 +536,7 @@ class QRCodeScanCubit extends Cubit { didKitProvider: didKitProvider, qrCodeScanCubit: qrCodeScanCubit, secureStorageProvider: getSecureStorage, - dioClient: dioClient, + dioClient: client, userPin: userPin, ); }, @@ -567,11 +564,71 @@ class QRCodeScanCubit extends Cubit { didKitProvider: didKitProvider, qrCodeScanCubit: qrCodeScanCubit, secureStorageProvider: getSecureStorage, - dioClient: dioClient, + dioClient: client, userPin: null, ); } + Future startOIDC4VCDeferedCredentialIssuance({ + required CredentialModel credentialModel, + }) async { + try { + emit(state.loading()); + final OIDC4VCType? currentOIIDC4VCTypeForIssuance = + getOIDC4VCTypeForIssuance(credentialModel.pendingInfo!.url); + + if (currentOIIDC4VCTypeForIssuance != null) { + await getAndAddDefferedCredential( + credentialModel: credentialModel, + credentialsCubit: credentialsCubit, + oidc4vcType: currentOIIDC4VCTypeForIssuance, + dioClient: client, + ); + } else { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ); + } + } catch (e) { + if (e is DioException) { + final error = NetworkException.getDioException(error: e); + if (error.message == NetworkError.NETWORK_ERROR_NOT_FOUND) { + /// the VC is not yet ready + + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_theCredentialIsNotReady, + ), + ); + } else if (error.message == NetworkError.NETWORK_ERROR_NOT_READY) { + /// the VC is no more ready..... + /// teh user call back teh issuer after 2 months + + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_theCredentialIsNoMoreReady, + ), + ); + } else { + emitError( + ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ); + } + } else { + emitError( + ResponseMessage( + ResponseString.RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ); + } + } + } + Future isVCPresentable( PresentationDefinition? presentationDefinition, ) async { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d041c4a12..184d19514 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -941,5 +941,7 @@ "placeholders": { "count": {} } - } + }, + "theCredentialIsNotReady": "The credential is not ready.", + "theCredentialIsNoMoreReady": "The ceredential is no more available." } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index eefd973f3..e7d486054 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -847,7 +847,9 @@ "userConsentForIssuerAccess", "userConsentForVerifierAccess", "userPINCodeForAuthentication", - "youcanSelectOnlyXCredential" + "youcanSelectOnlyXCredential", + "theCredentialIsNotReady", + "theCredentialIsNoMoreReady" ], "es": [ @@ -1698,7 +1700,9 @@ "userConsentForIssuerAccess", "userConsentForVerifierAccess", "userPINCodeForAuthentication", - "youcanSelectOnlyXCredential" + "youcanSelectOnlyXCredential", + "theCredentialIsNotReady", + "theCredentialIsNoMoreReady" ], "fr": [ @@ -1852,7 +1856,9 @@ "userConsentForIssuerAccess", "userConsentForVerifierAccess", "userPINCodeForAuthentication", - "youcanSelectOnlyXCredential" + "youcanSelectOnlyXCredential", + "theCredentialIsNotReady", + "theCredentialIsNoMoreReady" ], "it": [ @@ -2703,6 +2709,8 @@ "userConsentForIssuerAccess", "userConsentForVerifierAccess", "userPINCodeForAuthentication", - "youcanSelectOnlyXCredential" + "youcanSelectOnlyXCredential", + "theCredentialIsNotReady", + "theCredentialIsNoMoreReady" ] } diff --git a/lib/oidc4vc/add_oidc4vc_credential.dart b/lib/oidc4vc/add_oidc4vc_credential.dart index b5645ef3e..52a2c7b42 100644 --- a/lib/oidc4vc/add_oidc4vc_credential.dart +++ b/lib/oidc4vc/add_oidc4vc_credential.dart @@ -10,13 +10,13 @@ import 'package:jose/jose.dart'; Future addOIDC4VCCredential({ required dynamic encodedCredentialFromOIDC4VC, - required Uri uri, required CredentialsCubit credentialsCubit, required String issuer, required OIDC4VCType oidc4vcType, required String credentialType, required bool isLastCall, required String format, + String? credentialIdToBeDeleted, }) async { late Map credentialFromOIDC4VC; if (format == 'jwt_vc') { @@ -81,6 +81,14 @@ Future addOIDC4VCCredential({ activities: [Activity(acquisitionAt: DateTime.now())], ); + if (credentialIdToBeDeleted != null) { + ///delete pending dummy credential + await credentialsCubit.deleteById( + id: credentialIdToBeDeleted, + showMessage: false, + ); + } + // insert the credential in the wallet await credentialsCubit.insertCredential( credential: credentialModel, diff --git a/lib/oidc4vc/get_and_add_deffered_credential.dart b/lib/oidc4vc/get_and_add_deffered_credential.dart new file mode 100644 index 000000000..9f4e4436a --- /dev/null +++ b/lib/oidc4vc/get_and_add_deffered_credential.dart @@ -0,0 +1,36 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/credentials/credentials.dart'; +import 'package:altme/dashboard/dashboard.dart'; + +import 'package:altme/oidc4vc/oidc4vc.dart'; + +Future getAndAddDefferedCredential({ + required CredentialModel credentialModel, + required OIDC4VCType oidc4vcType, + required CredentialsCubit credentialsCubit, + required DioClient dioClient, +}) async { + final (_, issuer) = await getIssuerAndPreAuthorizedCode( + oidc4vcType: oidc4vcType, + scannedResponse: credentialModel.pendingInfo!.url, + dioClient: dioClient, + ); + + final dynamic encodedCredentialOrFutureToken = + await oidc4vcType.getOIDC4VC.getDeferredCredential( + acceptanceToken: credentialModel.pendingInfo!.acceptanceToken, + deferredCredentialEndpoint: + credentialModel.pendingInfo!.deferredCredentialEndpoint, + ); + + await addOIDC4VCCredential( + encodedCredentialFromOIDC4VC: encodedCredentialOrFutureToken, + credentialsCubit: credentialsCubit, + oidc4vcType: oidc4vcType, + issuer: issuer, + credentialType: credentialModel.credentialPreview.type[0], + isLastCall: true, + format: credentialModel.pendingInfo!.format, + credentialIdToBeDeleted: credentialModel.id, + ); +} diff --git a/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart b/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart index 6dffb7a6a..7d6bfb704 100644 --- a/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart +++ b/lib/oidc4vc/initiate_oidv4vc_credential_issuance.dart @@ -8,6 +8,7 @@ import 'package:altme/oidc4vc/add_oidc4vc_credential.dart'; import 'package:did_kit/did_kit.dart'; import 'package:oidc4vc/oidc4vc.dart'; import 'package:secure_storage/secure_storage.dart'; +import 'package:uuid/uuid.dart'; Future initiateOIDC4VCCredentialIssuance({ required String scannedResponse, @@ -81,34 +82,11 @@ Future getAndAddCredential({ }) async { final Uri uriFromScannedResponse = Uri.parse(scannedResponse); - String? preAuthorizedCode; - late String issuer; - - switch (oidc4vcType) { - case OIDC4VCType.DEFAULT: - case OIDC4VCType.HEDERA: - case OIDC4VCType.EBSIV3: - final dynamic credentialOfferJson = await getCredentialOfferJson( - scannedResponse: scannedResponse, - dioClient: dioClient, - ); - if (credentialOfferJson == null) throw Exception(); - - preAuthorizedCode = credentialOfferJson['grants'] - ['urn:ietf:params:oauth:grant-type:pre-authorized_code'] - ['pre-authorized_code'] - .toString(); - issuer = credentialOfferJson['credential_issuer'].toString(); - - case OIDC4VCType.GAIAX: - case OIDC4VCType.EBSIV2: - issuer = uriFromScannedResponse.queryParameters['issuer'].toString(); - preAuthorizedCode = - uriFromScannedResponse.queryParameters['pre-authorized_code']; - - case OIDC4VCType.JWTVC: - throw Exception(); - } + final (preAuthorizedCode, issuer) = await getIssuerAndPreAuthorizedCode( + oidc4vcType: oidc4vcType, + scannedResponse: scannedResponse, + dioClient: dioClient, + ); final mnemonic = await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); @@ -125,8 +103,11 @@ Future getAndAddCredential({ ); if (preAuthorizedCode != null) { - final (dynamic encodedCredentialFromOIDC4VC, String format) = - await oidc4vc.getCredential( + final ( + dynamic encodedCredentialOrFutureToken, + String? deferredCredentialEndpoint, + String format + ) = await oidc4vc.getCredential( preAuthorizedCode: preAuthorizedCode, issuer: issuer, credential: credential, @@ -137,19 +118,61 @@ Future getAndAddCredential({ indexValue: oidc4vcType.indexValue, userPin: userPin, ); - final String credentialType = getCredentialData(credential); - - await addOIDC4VCCredential( - encodedCredentialFromOIDC4VC: encodedCredentialFromOIDC4VC, - uri: uriFromScannedResponse, - credentialsCubit: credentialsCubit, - oidc4vcType: oidc4vcType, - issuer: issuer, - credentialType: credentialType, - isLastCall: isLastCall, - format: format, - ); + final acceptanceToken = encodedCredentialOrFutureToken['acceptance_token']; + + if (acceptanceToken != null && deferredCredentialEndpoint != null) { + /// add deferred card + final id = const Uuid().v4(); + + final credentialModel = CredentialModel( + id: id, + credentialPreview: Credential( + 'dummy1', + ['dummy2'], + [credentialType], + 'dummy4', + 'dummy5', + '', + [Proof.dummy()], + DefaultCredentialSubjectModel( + id: 'dummy7', + type: 'dummy8', + issuedBy: const Author(''), + ), + [Translation('en', '')], + [Translation('en', '')], + CredentialStatusField.emptyCredentialStatusField(), + [Evidence.emptyEvidence()], + ), + data: const {}, + display: Display.emptyDisplay(), + image: '', + shareLink: '', + pendingInfo: PendingInfo( + acceptanceToken: acceptanceToken.toString(), + deferredCredentialEndpoint: deferredCredentialEndpoint, + format: format, + url: scannedResponse, + ), + ); + // insert the credential in the wallet + await credentialsCubit.insertCredential( + credential: credentialModel, + showStatus: false, + showMessage: isLastCall, + ); + } else { + await addOIDC4VCCredential( + encodedCredentialFromOIDC4VC: encodedCredentialOrFutureToken, + credentialsCubit: credentialsCubit, + oidc4vcType: oidc4vcType, + issuer: issuer, + credentialType: credentialType, + isLastCall: isLastCall, + format: format, + ); + } } else { final Uri ebsiAuthenticationUri = await oidc4vc.getAuthorizationUriForIssuer( diff --git a/lib/oidc4vc/oidc4vc.dart b/lib/oidc4vc/oidc4vc.dart index 8b1d3dd4e..5553cdbe4 100644 --- a/lib/oidc4vc/oidc4vc.dart +++ b/lib/oidc4vc/oidc4vc.dart @@ -1,3 +1,4 @@ export 'add_oidc4vc_credential.dart'; +export 'get_and_add_deffered_credential.dart'; export 'initiate_oidv4vc_credential_issuance.dart'; export 'verify_encoded_data.dart'; diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 2de49e08e..ce0a7cf93 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -9,7 +9,7 @@ import 'package:altme/l10n/l10n.dart'; import 'package:altme/polygon_id/polygon_id.dart'; import 'package:altme/splash/splash.dart'; import 'package:altme/theme/app_theme/app_theme.dart'; -import 'package:dio/dio.dart'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' as services; @@ -154,7 +154,6 @@ class _SplashViewState extends State { scannedResponse: uri.toString(), currentOIIDC4VCType: currentOIIDC4VCTypeForIssuance, qrCodeScanCubit: context.read(), - dioClient: DioClient('', Dio()), ); } } diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 2d62f0c24..ec6114c8a 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -196,7 +196,7 @@ class OIDC4VC { String? accessToken; /// Retreive credential_type from url - Future<(dynamic, String)> getCredential({ + Future<(dynamic, String?, String)> getCredential({ required String issuer, required dynamic credential, required String did, @@ -270,7 +270,30 @@ class OIDC4VC { nonce = credentialResponse.data['c_nonce'].toString(); - return (credentialResponse.data, format); + String? deferredCredentialEndpoint; + + if (openidConfigurationResponse['deferred_credential_endpoint'] != null) { + deferredCredentialEndpoint = + openidConfigurationResponse['deferred_credential_endpoint'] + .toString(); + } + + return (credentialResponse.data, deferredCredentialEndpoint, format); + } + + /// get Deferred credential from url + Future getDeferredCredential({ + required String acceptanceToken, + required String deferredCredentialEndpoint, + }) async { + final credentialHeaders = buildCredentialHeaders(acceptanceToken); + + final dynamic credentialResponse = await client.post( + deferredCredentialEndpoint, + options: Options(headers: credentialHeaders), + ); + + return credentialResponse.data; } void resetNonceAndAccessToken() {