From 5f0afeca55ecffb7fd8abedb31ac4c7541fad1f4 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 6 Oct 2023 13:32:58 +0545 Subject: [PATCH 1/5] feat: Remove Add card option from pending cards category #1931 --- .../list/widgets/home_credential_category_item.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart index 54e8aa1bd..9589bdbd1 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_category_item.dart @@ -64,6 +64,9 @@ class HomeCredentialCategoryItem extends StatelessWidget { itemCount: sortedCredentials.length + 1, itemBuilder: (_, index) { if (index == sortedCredentials.length) { + if (credentialCategory == CredentialCategory.pendingCards) { + return Container(); + } return AddCredentialButton( credentialCategory: credentialCategory, ); From 9963997557e3b37156f2f978d8fe40dd5c096bdf Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 6 Oct 2023 14:51:37 +0545 Subject: [PATCH 2/5] feat: Added detaisl on the deferred VC #1878 --- .../detail/view/credentials_details_page.dart | 25 +++++++++++++++++++ .../models/pending_info/pending_info.dart | 4 +++ lib/l10n/arb/app_en.arb | 3 ++- lib/l10n/untranslated.json | 12 ++++++--- lib/oidc4vc/get_and_add_credential.dart | 2 ++ 5 files changed, 41 insertions(+), 5 deletions(-) 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 6068ed640..1f207efa0 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 @@ -272,6 +272,31 @@ class _CredentialsDetailsViewState extends State { Theme.of(context).colorScheme.valueColor, ), ], + if (widget.credentialModel.pendingInfo != null) ...[ + CredentialField( + padding: const EdgeInsets.symmetric( + horizontal: 0, + vertical: 8, + ), + title: l10n.issuer, + value: widget.credentialModel.pendingInfo!.issuer, + titleColor: + Theme.of(context).colorScheme.titleColor, + valueColor: + Theme.of(context).colorScheme.valueColor, + ), + CredentialField( + padding: EdgeInsets.zero, + title: l10n.dateOfRequest, + value: UiDate.formatDate( + widget.credentialModel.pendingInfo!.requestedAt, + ), + titleColor: + Theme.of(context).colorScheme.titleColor, + valueColor: + Theme.of(context).colorScheme.valueColor, + ), + ], ], if (state.credentialDetailTabStatus == CredentialDetailTabStatus.activity) ...[ 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 index 28169c7cf..48f299e2f 100644 --- 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 @@ -10,6 +10,8 @@ class PendingInfo extends Equatable { required this.deferredCredentialEndpoint, required this.format, required this.url, + required this.issuer, + required this.requestedAt, }); factory PendingInfo.fromJson(Map json) => @@ -19,6 +21,8 @@ class PendingInfo extends Equatable { final String deferredCredentialEndpoint; final String format; final String url; + final String issuer; + final DateTime requestedAt; Map toJson() => _$PendingInfoToJson(this); diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8881c83ec..dd181737b 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -978,6 +978,7 @@ "credentialIssuanceDenied": "Credential issuance denied", "thisCredentialFormatIsNotSupported": "This credential format is not supported", "moreDetails": "More Details", - "theCredentialOfferIsInvalid": "The credential offer is invalid" + "theCredentialOfferIsInvalid": "The credential offer is invalid", + "dateOfRequest": "Date of Request" } diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 1f10c7df4..b4a71f211 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -884,7 +884,8 @@ "credentialIssuanceDenied", "thisCredentialFormatIsNotSupported", "moreDetails", - "theCredentialOfferIsInvalid" + "theCredentialOfferIsInvalid", + "dateOfRequest" ], "es": [ @@ -1772,7 +1773,8 @@ "credentialIssuanceDenied", "thisCredentialFormatIsNotSupported", "moreDetails", - "theCredentialOfferIsInvalid" + "theCredentialOfferIsInvalid", + "dateOfRequest" ], "fr": [ @@ -1963,7 +1965,8 @@ "credentialIssuanceDenied", "thisCredentialFormatIsNotSupported", "moreDetails", - "theCredentialOfferIsInvalid" + "theCredentialOfferIsInvalid", + "dateOfRequest" ], "it": [ @@ -2851,6 +2854,7 @@ "credentialIssuanceDenied", "thisCredentialFormatIsNotSupported", "moreDetails", - "theCredentialOfferIsInvalid" + "theCredentialOfferIsInvalid", + "dateOfRequest" ] } diff --git a/lib/oidc4vc/get_and_add_credential.dart b/lib/oidc4vc/get_and_add_credential.dart index 947a93ebf..a8391154c 100644 --- a/lib/oidc4vc/get_and_add_credential.dart +++ b/lib/oidc4vc/get_and_add_credential.dart @@ -101,6 +101,8 @@ Future getAndAddCredential({ deferredCredentialEndpoint: deferredCredentialEndpoint, format: format, url: scannedResponse, + issuer: issuer, + requestedAt: DateTime.now(), ), ); // insert the credential in the wallet From ce45fc1dc7bb0237b50738e28eecedc3e1adfb24 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 6 Oct 2023 14:56:16 +0545 Subject: [PATCH 3/5] Made scope parameter compulsary for id_token only --- .../cubit/qr_code_scan_cubit.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 b6d391714..a2ce80814 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 @@ -605,17 +605,6 @@ class QRCodeScanCubit extends Cubit { } } - final scope = state.uri!.queryParameters['scope']; - if (scope == null || scope != 'openid') { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': - 'The openid scope is required in the scope list.', - }, - ); - } - final redirectUri = state.uri!.queryParameters['redirect_uri']; final clientId = state.uri!.queryParameters['client_id']; final isUrl = isURL(clientId.toString()); @@ -624,6 +613,17 @@ class QRCodeScanCubit extends Cubit { if (responseType == 'id_token') { /// verifier side (siopv2) + final scope = state.uri!.queryParameters['scope']; + if (scope == null || scope != 'openid') { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': + 'The openid scope is required in the scope list.', + }, + ); + } + if (redirectUri == null) { throw ResponseMessage( data: { From 31a80db6a12cd38229f87077a6bd9febf7fdd830 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 9 Oct 2023 15:32:29 +0545 Subject: [PATCH 4/5] feat: Add did:key P256 in menu #1980 --- .../shared/constants/secure_storage_keys.dart | 1 + .../helper_functions/helper_functions.dart | 30 +++- .../drawer/ssi/manage_did/manage_did.dart | 2 + .../drawer/ssi/manage_did/view/did_menu.dart | 6 + .../did_p256/did_p256_private_key_page.dart | 135 ++++++++++++++++++ .../view/did_p256/manage_did_p256_page.dart | 77 ++++++++++ lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 +- 8 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart create mode 100644 lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index 122ed041c..c10a1a5cc 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -61,6 +61,7 @@ class SecureStorageKeys { static const String ssiKey = 'ssi/key'; static const String p256PrivateKey = 'p256PrivateKey'; + static const String p256PrivateKey2 = 'p256PrivateKey2'; static const String cryptoAccount = 'cryptoAccount'; static const String cryptoAccounTrackingIndex = 'cryptoAccounTrackingIndex'; diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 09797bcbd..4fc45c4d5 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -245,7 +245,7 @@ Future web3RpcMainnetInfuraURL() async { return '$prefixUrl$infuraApiKey'; } -Future getRandomP256PrivateKey( +Future getEBSIV3P256PrivateKey( SecureStorageProvider secureStorage, ) async { final String? p256PrivateKey = await secureStorage.get( @@ -273,6 +273,32 @@ Future getRandomP256PrivateKey( } } +Future getP256PrivateKey(SecureStorageProvider secureStorage) async { + final String? p256PrivateKey = await secureStorage.get( + SecureStorageKeys.p256PrivateKey2, + ); + + if (p256PrivateKey == null) { + final jwk = JsonWebKey.generate('ES256'); + + final json = jwk.toJson(); + + // Sort the keys in ascending order and remove alg + final sortedJwk = Map.fromEntries( + json.entries.toList()..sort((e1, e2) => e1.key.compareTo(e2.key)), + )..remove('keyOperations'); + + await secureStorage.set( + SecureStorageKeys.p256PrivateKey2, + jsonEncode(sortedJwk), + ); + + return jsonEncode(sortedJwk); + } else { + return p256PrivateKey; + } +} + Future fetchPrivateKey({ required OIDC4VC oidc4vc, required SecureStorageProvider secureStorage, @@ -283,7 +309,7 @@ Future fetchPrivateKey({ final index = getIndexValue(isEBSIV3: true); if (isEBSIV3) { - privateKey = await getRandomP256PrivateKey(getSecureStorage); + privateKey = await getEBSIV3P256PrivateKey(getSecureStorage); } else { final OIDC4VC oidc4vc = OIDC4VC(); final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); diff --git a/lib/dashboard/drawer/ssi/manage_did/manage_did.dart b/lib/dashboard/drawer/ssi/manage_did/manage_did.dart index f7faa908c..45b894fd3 100644 --- a/lib/dashboard/drawer/ssi/manage_did/manage_did.dart +++ b/lib/dashboard/drawer/ssi/manage_did/manage_did.dart @@ -4,6 +4,8 @@ export 'view/did_ebsi_v3/did_ebsi_v3_private_key_page.dart'; export 'view/did_ebsi_v3/manage_did_ebsi_v3_page.dart'; export 'view/did_edDSA/did_ed_dsa_private_key_page.dart'; export 'view/did_edDSA/manage_did_ed_dsa_page.dart'; +export 'view/did_p256/did_p256_private_key_page.dart'; +export 'view/did_p256/manage_did_p256_page.dart'; export 'view/did_polygon_id/manage_did_polygon_id_page.dart'; export 'view/did_secp256k1/did_secp256k1_private_key_page.dart'; export 'view/did_secp256k1/manage_did_secp256k1_page.dart'; diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart index 4c3c57d74..ee3d66b4a 100644 --- a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart @@ -68,6 +68,12 @@ class DidView extends StatelessWidget { .push(ManageDidEbsiV3Page.route()); }, ), + DrawerItem( + title: l10n.keyDecentralizedIDP256, + onTap: () { + Navigator.of(context).push(ManageDidP256Page.route()); + }, + ), DrawerItem( title: l10n.polygonDecentralizedID, onTap: () async { diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart new file mode 100644 index 000000000..f50602290 --- /dev/null +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart @@ -0,0 +1,135 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class DidP256PrivateKeyPage extends StatefulWidget { + const DidP256PrivateKeyPage({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const DidP256PrivateKeyPage(), + settings: const RouteSettings(name: '/DidP256PrivateKeyPage'), + ); + } + + @override + State createState() => _DidP256PrivateKeyPageState(); +} + +class _DidP256PrivateKeyPageState extends State + with SingleTickerProviderStateMixin { + late Animation animation; + late AnimationController animationController; + + Future getKey() async { + final privateKey = await getP256PrivateKey(getSecureStorage); + return privateKey; + } + + @override + void initState() { + super.initState(); + animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 20), + ); + + final Tween rotationTween = Tween(begin: 20, end: 0); + + animation = rotationTween.animate(animationController) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + Navigator.pop(context); + } + }); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + scrollView: false, + title: l10n.decentralizedIDKey, + titleAlignment: Alignment.topCenter, + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: BackgroundCard( + width: double.infinity, + height: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + l10n.didPrivateKey, + style: Theme.of(context).textTheme.title, + ), + const SizedBox( + height: Sizes.spaceNormal, + ), + FutureBuilder( + future: getKey(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return Column( + children: [ + Text( + snapshot.data!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: Sizes.spaceXLarge), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: snapshot.data!), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + ], + ); + case ConnectionState.waiting: + case ConnectionState.none: + case ConnectionState.active: + return const Text(''); + } + }, + ), + Expanded( + child: Center( + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return Text( + timeFormatter(timeInSecond: animation.value.toInt()), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.displayMedium, + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart new file mode 100644 index 000000000..bb042ff46 --- /dev/null +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart @@ -0,0 +1,77 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:flutter/material.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class ManageDidP256Page extends StatefulWidget { + const ManageDidP256Page({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const ManageDidP256Page(), + settings: const RouteSettings(name: '/ManageDidP256Page'), + ); + } + + @override + State createState() => _ManageDidP256PageState(); +} + +class _ManageDidP256PageState extends State { + Future getDid() async { + final privateKey = await getP256PrivateKey(getSecureStorage); + + final (did, _) = await getDidAndKid( + isEBSIV3: false, + privateKey: privateKey, + didKitProvider: DIDKitProvider(), + ); + + return did; + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + title: l10n.keyDecentralizedIDP256, + titleAlignment: Alignment.topCenter, + scrollView: false, + titleLeading: const BackLeadingButton(), + body: BackgroundCard( + height: double.infinity, + width: double.infinity, + padding: const EdgeInsets.all(Sizes.spaceSmall), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + FutureBuilder( + future: getDid(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + final did = snapshot.data!; + return Did(did: did); + case ConnectionState.waiting: + case ConnectionState.none: + case ConnectionState.active: + return const SizedBox(); + } + }, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: Sizes.spaceNormal), + child: Divider(), + ), + DidPrivateKey(route: DidP256PrivateKeyPage.route()), + ], + ), + ), + ); + } +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index dd181737b..3fefa9d29 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -979,6 +979,7 @@ "thisCredentialFormatIsNotSupported": "This credential format is not supported", "moreDetails": "More Details", "theCredentialOfferIsInvalid": "The credential offer is invalid", - "dateOfRequest": "Date of Request" + "dateOfRequest": "Date of Request", + "keyDecentralizedIDP256": "Key Decentralized ID P-256" } diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index b4a71f211..c4e8addf1 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -885,7 +885,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "es": [ @@ -1774,7 +1775,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "fr": [ @@ -1966,7 +1968,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "it": [ @@ -2855,6 +2858,7 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ] } From 8de0063d241fb2e7a2c3dd9c456e5a1384a5293b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 9 Oct 2023 15:47:21 +0545 Subject: [PATCH 5/5] version update --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d441355a5..9201b7ead 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: archive - sha256: "06a96f1249f38a00435b3b0c9a3246d934d7dbc8183fc7c9e56989860edb99d4" + sha256: ca12e6c9ac022f33fd89128e7007fb5e97ab6e814d4fa05dd8d4f2db1e3c69cb url: "https://pub.dev" source: hosted - version: "3.4.4" + version: "3.4.5" args: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 900ef2788..4df8a5b48 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.21.1+287 +version: 1.21.2+288 publish_to: none environment: