From e6eb623b7b8c395176aea9ba4d5b72c5139a4cf9 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 23 Oct 2023 16:23:33 +0545 Subject: [PATCH] feat: Added developer mode dialog for OIDC4VCI authorization flow #1998 --- .../shared/enum/status/qr_scan_status.dart | 1 + .../helper_functions/helper_functions.dart | 14 +++ .../cubit/qr_code_scan_cubit.dart | 71 ++++-------- .../get_authorization_uri_for_issuer.dart | 6 +- lib/scan/cubit/scan_cubit.dart | 2 +- lib/splash/bloclisteners/blocklisteners.dart | 108 ++++++++++++++++++ lib/splash/view/splash_page.dart | 2 +- pubspec.lock | 8 +- 8 files changed, 155 insertions(+), 57 deletions(-) diff --git a/lib/app/shared/enum/status/qr_scan_status.dart b/lib/app/shared/enum/status/qr_scan_status.dart index 502b90926..d61d51d66 100644 --- a/lib/app/shared/enum/status/qr_scan_status.dart +++ b/lib/app/shared/enum/status/qr_scan_status.dart @@ -3,6 +3,7 @@ enum QrScanStatus { idle, loading, acceptHost, + authorizationFlow, error, success, goBack, diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 32844e0b3..48d7c1bfe 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -1045,6 +1045,20 @@ ${openidConfigurationResponse != null ? const JsonEncoder.withIndent(' ').conve '''; } +String getFormattedStringOIDC4VCIAuthorizedCodeFlow({ + required String url, + Map? statePayload, + Map? codeForAuthorisedFlowPayload, +}) { + return ''' +SCHEME : ${getSchemeFromUrl(url)}\n +STATE : +${statePayload != null ? const JsonEncoder.withIndent(' ').convert(statePayload) : 'None'}\n +CODE : +${codeForAuthorisedFlowPayload != null ? const JsonEncoder.withIndent(' ').convert(codeForAuthorisedFlowPayload) : 'None'} +'''; +} + Future getFormattedStringOIDC4VPSIOPV2({ required String url, required DioClient client, 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 8326e0459..d16c36dd8 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 @@ -1024,7 +1024,7 @@ class QRCodeScanCubit extends Cubit { if (url != null) { final uri = Uri.parse(url); if (uri.toString().startsWith(Parameters.oidc4vcUniversalLink)) { - await authorizedFlowCompletion(uri); + await authorizedFlowStart(uri); return; } } @@ -1135,51 +1135,24 @@ class QRCodeScanCubit extends Cubit { ); } - Future authorizedFlowCompletion(Uri uri) async { - try { - final error = uri.queryParameters['error']; - final errorDescription = uri.queryParameters['error_description']; - - if (error != null) { - throw ResponseMessage( - data: { - 'error': error, - 'error_description': errorDescription, - }, - ); - } - - final codeForAuthorisedFlow = uri.queryParameters['code']; - final state = uri.queryParameters['state']; - - if (codeForAuthorisedFlow == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'The code is missing.', - }, - ); - } - if (state == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'The state is missing.', - }, - ); - } - await dotenv.load(); - final String authorizationUriSecretKey = - dotenv.get('AUTHORIZATION_URI_SECRET_KEY'); - - final jwt = JWT.verify(state, SecretKey(authorizationUriSecretKey)); - - final payload = jwt.payload as Map; + Future authorizedFlowStart(Uri uri) async { + emit( + state.copyWith( + uri: uri, + qrScanStatus: QrScanStatus.authorizationFlow, + ), + ); + } - final containsAllRequiredKey = payload.containsKey('credentials') && - payload.containsKey('codeVerifier') && - payload.containsKey('issuer') && - payload.containsKey('isEBSIV3'); + Future authorizedFlowCompletion({ + required Map statePayload, + required String codeForAuthorisedFlow, + }) async { + try { + final containsAllRequiredKey = statePayload.containsKey('credentials') && + statePayload.containsKey('codeVerifier') && + statePayload.containsKey('issuer') && + statePayload.containsKey('isEBSIV3'); if (!containsAllRequiredKey) { throw ResponseMessage( @@ -1190,10 +1163,10 @@ class QRCodeScanCubit extends Cubit { ); } - final selectedCredentials = payload['credentials'] as List; - final String codeVerifier = payload['codeVerifier'].toString(); - final String issuer = payload['issuer'].toString(); - final bool isEBSIV3 = payload['isEBSIV3'] as bool; + final selectedCredentials = statePayload['credentials'] as List; + final String codeVerifier = statePayload['codeVerifier'].toString(); + final String issuer = statePayload['issuer'].toString(); + final bool isEBSIV3 = statePayload['isEBSIV3'] as bool; await addCredentialsInLoop( selectedCredentials: selectedCredentials, diff --git a/lib/oidc4vc/get_authorization_uri_for_issuer.dart b/lib/oidc4vc/get_authorization_uri_for_issuer.dart index d629e524b..59e0c9fb9 100644 --- a/lib/oidc4vc/get_authorization_uri_for_issuer.dart +++ b/lib/oidc4vc/get_authorization_uri_for_issuer.dart @@ -51,7 +51,8 @@ Future getAuthorizationUriForIssuer({ final jwtToken = jwt.sign(SecretKey(authorizationUriSecretKey)); - final Uri ebsiAuthenticationUri = await oidc4vc.getAuthorizationUriForIssuer( + final Uri oidc4vcAuthenticationUri = + await oidc4vc.getAuthorizationUriForIssuer( selectedCredentials: selectedCredentials, clientId: did, redirectUri: Parameters.oidc4vcUniversalLink, @@ -62,5 +63,6 @@ Future getAuthorizationUriForIssuer({ state: jwtToken, authorizationEndPoint: Parameters.authorizeEndPoint, ); - await LaunchUrl.launchUri(ebsiAuthenticationUri); + + await LaunchUrl.launchUri(oidc4vcAuthenticationUri); } diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index aec62c9ef..59fb1924c 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -573,7 +573,7 @@ class ScanCubit extends Cubit { final uri = Uri.parse(url); if (uri.toString().startsWith(Parameters.oidc4vcUniversalLink)) { emit(state.copyWith(status: ScanStatus.goBack)); - await qrCodeScanCubit.authorizedFlowCompletion(uri); + await qrCodeScanCubit.authorizedFlowStart(uri); return; } } else { diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 05250383a..89e4b7cc6 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -14,10 +14,12 @@ import 'package:altme/scan/scan.dart'; import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:beacon_flutter/beacon_flutter.dart'; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:polygonid/polygonid.dart'; import 'package:share_plus/share_plus.dart'; @@ -417,6 +419,112 @@ final qrCodeBlocListener = BlocListener( } } + if (state.status == QrScanStatus.authorizationFlow) { + try { + if (state.uri != null) { + LoadingView().show(context: context); + final profileCubit = context.read(); + final uri = state.uri!; + final error = uri.queryParameters['error']; + final errorDescription = uri.queryParameters['error_description']; + + if (error != null) { + throw ResponseMessage( + data: { + 'error': error, + 'error_description': errorDescription, + }, + ); + } + + final codeForAuthorisedFlow = uri.queryParameters['code']; + final stateValue = uri.queryParameters['state']; + + if (codeForAuthorisedFlow == null) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'The code is missing.', + }, + ); + } + if (stateValue == null) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'The state is missing.', + }, + ); + } + + await dotenv.load(); + final String authorizationUriSecretKey = + dotenv.get('AUTHORIZATION_URI_SECRET_KEY'); + + final jwt = + JWT.verify(stateValue, SecretKey(authorizationUriSecretKey)); + + final statePayload = jwt.payload as Map; + + /// if dev mode is ON show some dialog to show data + if (profileCubit.state.model.isDeveloperMode) { + final codeForAuthorisedFlowPayload = + JWTDecode().parseJwt(codeForAuthorisedFlow); + + final String formattedData = + getFormattedStringOIDC4VCIAuthorizedCodeFlow( + url: state.uri.toString(), + codeForAuthorisedFlowPayload: codeForAuthorisedFlowPayload, + statePayload: statePayload, + ); + + LoadingView().hide(); + final bool moveAhead = await showDialog( + context: context, + builder: (_) { + return DeveloperModeDialog( + onDisplay: () async { + Navigator.of(context).pop(false); + await Navigator.of(context).push( + JsonViewerPage.route( + title: l10n.displayConfiguration, + data: formattedData, + ), + ); + return; + }, + onDownload: () { + Navigator.of(context).pop(false); + + final box = context.findRenderObject() as RenderBox?; + final subject = l10n.shareWith; + + Share.share( + formattedData, + subject: subject, + sharePositionOrigin: + box!.localToGlobal(Offset.zero) & box.size, + ); + }, + onSkip: () { + Navigator.of(context).pop(true); + }, + ); + }, + ) ?? + true; + if (!moveAhead) return; + } + await context.read().authorizedFlowCompletion( + statePayload: statePayload, + codeForAuthorisedFlow: codeForAuthorisedFlow, + ); + } + } catch (e) { + context.read().emitError(e); + } + } + if (state.status == QrScanStatus.success) { if (state.route != null) { await Navigator.of(context).push(state.route!); diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 1023c9b81..7703fb5c7 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -90,7 +90,7 @@ class _SplashViewState extends State { } if (uri.toString().startsWith(Parameters.oidc4vcUniversalLink)) { - await context.read().authorizedFlowCompletion(uri!); + await context.read().authorizedFlowStart(uri!); return; } diff --git a/pubspec.lock b/pubspec.lock index 507a64fd9..a6c694f19 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -989,10 +989,10 @@ packages: dependency: "direct main" description: name: flutter_olm - sha256: e6de66f733f18f552ca387745464b2bb2997d3f9ba3ae53ca6e930b80af57f08 + sha256: "69aaac45d854e74d17d04dac8a0ca3f548266d271a0f0fa7600e006e81432417" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" flutter_openssl_crypto: dependency: "direct main" description: @@ -1525,10 +1525,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "2fbc3914fe625e196c64ea8ffc4084cd36781d2be276d4d5923b11af3b5d44ff" + sha256: d1ce638a53b3d751a6cdbd61ed2705d20d38aa30ed26b6fb2526c34c934afe54 url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.5.0" mockingjay: dependency: "direct dev" description: