From 9cb1458e683b587f882a5924e45ea362b0e6f1bd Mon Sep 17 00:00:00 2001 From: Wes Date: Wed, 3 Jan 2024 17:28:16 -0700 Subject: [PATCH] feat: use real widget uris for PFi --- frontend/ios/Runner/Info.plist | 2 +- .../features/account/account_did_page.dart | 2 +- .../features/account/account_providers.dart | 3 +- frontend/lib/features/home/home_page.dart | 23 ++++++---- frontend/lib/features/pfis/pfi.dart | 4 +- frontend/lib/features/pfis/pfi_providers.dart | 10 +++-- .../features/pfis/pfi_verification_page.dart | 8 ++-- frontend/lib/main.dart | 26 ++++++------ .../services/secure_storage_key_manager.dart | 42 +++++++++++++++++++ frontend/pubspec.lock | 30 +++++++++++-- 10 files changed, 116 insertions(+), 34 deletions(-) create mode 100644 frontend/lib/services/secure_storage_key_manager.dart diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist index 0ceb679b..70491943 100644 --- a/frontend/ios/Runner/Info.plist +++ b/frontend/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Flutter Starter + DIDPay CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/frontend/lib/features/account/account_did_page.dart b/frontend/lib/features/account/account_did_page.dart index 53e8d145..58d642b7 100644 --- a/frontend/lib/features/account/account_did_page.dart +++ b/frontend/lib/features/account/account_did_page.dart @@ -13,7 +13,7 @@ class AccountDidPage extends HookConsumerWidget { appBar: AppBar(title: const Text('My DID')), body: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Center(child: SelectableText(did))), + child: Center(child: SelectableText(did.uri))), ); } } diff --git a/frontend/lib/features/account/account_providers.dart b/frontend/lib/features/account/account_providers.dart index b240aa00..e1be6122 100644 --- a/frontend/lib/features/account/account_providers.dart +++ b/frontend/lib/features/account/account_providers.dart @@ -1,3 +1,4 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:tbdex/tbdex.dart'; -final didProvider = Provider((ref) => throw UnimplementedError()); +final didProvider = Provider((ref) => throw UnimplementedError()); diff --git a/frontend/lib/features/home/home_page.dart b/frontend/lib/features/home/home_page.dart index dcae11e0..8a917a20 100644 --- a/frontend/lib/features/home/home_page.dart +++ b/frontend/lib/features/home/home_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_starter/features/pfis/pfi_providers.dart'; import 'package:flutter_starter/features/pfis/pfi_verification_page.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:tbdex/tbdex.dart'; class HomePage extends HookConsumerWidget { const HomePage({super.key}); @@ -16,14 +17,22 @@ class HomePage extends HookConsumerWidget { ...pfis.map( (pfi) => ListTile( title: Text(pfi.name), - subtitle: Text(pfi.id), + subtitle: Text(pfi.didUri), trailing: const Icon(Icons.chevron_right), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => PfiVerificationPage(pfi: pfi), - ), - ); + onTap: () async { + final result = await DidDht.resolve(pfi.didUri); + final widgetService = result.didDocument?.service + ?.firstWhere((e) => e.type == 'kyc-widget'); + if (widgetService?.serviceEndpoint != null) { + // ignore: use_build_context_synchronously + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => PfiVerificationPage( + widgetUri: widgetService!.serviceEndpoint, + ), + ), + ); + } }, ), ) diff --git a/frontend/lib/features/pfis/pfi.dart b/frontend/lib/features/pfis/pfi.dart index 232c79a1..64729d36 100644 --- a/frontend/lib/features/pfis/pfi.dart +++ b/frontend/lib/features/pfis/pfi.dart @@ -1,11 +1,11 @@ class Pfi { final String id; final String name; - final String widgetUrl; + final String didUri; Pfi({ required this.id, required this.name, - required this.widgetUrl, + required this.didUri, }); } diff --git a/frontend/lib/features/pfis/pfi_providers.dart b/frontend/lib/features/pfis/pfi_providers.dart index 0a8b6426..0b94ed9e 100644 --- a/frontend/lib/features/pfis/pfi_providers.dart +++ b/frontend/lib/features/pfis/pfi_providers.dart @@ -3,16 +3,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; final pfisProvider = Provider>( (ref) => [ + Pfi( + id: 'prototype', + name: 'Prototype PFI', + didUri: 'did:dht:3x1hbjobt577amnoeoxcenqrbjicym5mgsx6c6zszisf1igfj51y', + ), Pfi( id: 'africa', name: 'Africa', - widgetUrl: 'https://tbd.website', + didUri: 'coming soon...', ), Pfi( id: 'mexico', name: 'Mexico', - widgetUrl: - 'https://robustdisastrousstartups.wesbillman.repl.co/?proof=moeisreal&callback_uri=didpay://wes', + didUri: 'coming soon...', ), ], ); diff --git a/frontend/lib/features/pfis/pfi_verification_page.dart b/frontend/lib/features/pfis/pfi_verification_page.dart index 7f63391b..3539165b 100644 --- a/frontend/lib/features/pfis/pfi_verification_page.dart +++ b/frontend/lib/features/pfis/pfi_verification_page.dart @@ -1,14 +1,14 @@ import 'package:webview_flutter/webview_flutter.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_starter/features/pfis/pfi.dart'; class PfiVerificationPage extends StatelessWidget { - final Pfi pfi; + final String widgetUri; - const PfiVerificationPage({required this.pfi, super.key}); + const PfiVerificationPage({required this.widgetUri, super.key}); @override Widget build(BuildContext context) { + final fullPath = '$widgetUri?proof=moegrammer&callback_uri=didpay://kyc'; final controller = WebViewController() ..setBackgroundColor(Theme.of(context).colorScheme.background) ..setNavigationDelegate( @@ -28,7 +28,7 @@ class PfiVerificationPage extends StatelessWidget { }, ), ) - ..loadRequest(Uri.parse(pfi.widgetUrl)); + ..loadRequest(Uri.parse(fullPath)); return Scaffold( appBar: AppBar(title: const Text('PFI Verification')), diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 2058c304..839d72ad 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_starter/features/account/account_providers.dart'; import 'package:flutter_starter/features/app/app.dart'; +import 'package:flutter_starter/services/secure_storage_key_manager.dart'; import 'package:flutter_starter/services/service_providers.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:tbdex/tbdex.dart'; @@ -14,8 +15,8 @@ void main() async { aOptions: AndroidOptions(encryptedSharedPreferences: true), ); - final did = await getOrCreateDid(storage); - + final keyManager = SecureStorageKeyManager(storage); + final did = await getOrCreateDid(keyManager, storage); runApp(ProviderScope( overrides: [ secureStorage.overrideWithValue(storage), @@ -25,16 +26,17 @@ void main() async { )); } -Future getOrCreateDid(FlutterSecureStorage storage) async { - const didKey = 'did'; - final did = await storage.read(key: didKey); - if (did != null) { - return did; +Future getOrCreateDid( + KeyManager keyManager, + FlutterSecureStorage storage, +) async { + const didUriKey = 'did.uri'; + final didUri = await storage.read(key: didUriKey); + if (didUri != null) { + return DidJwk(uri: didUri, keyManager: keyManager); } - final keyManager = InMemoryKeyManager(); - final jwt = await DidJwk.create(keyManager: keyManager); - await storage.write(key: didKey, value: jwt.uri); - - return jwt.uri; + final did = await DidJwk.create(keyManager: keyManager); + await storage.write(key: didUriKey, value: did.uri); + return did; } diff --git a/frontend/lib/services/secure_storage_key_manager.dart b/frontend/lib/services/secure_storage_key_manager.dart new file mode 100644 index 00000000..5be4e0c8 --- /dev/null +++ b/frontend/lib/services/secure_storage_key_manager.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:tbdex/tbdex.dart'; + +class SecureStorageKeyManager implements KeyManager { + final FlutterSecureStorage _storage; + SecureStorageKeyManager(this._storage); + + @override + Future generatePrivateKey(DsaName alg) async { + final privateKeyJwk = await DsaAlgorithms.generatePrivateKey(alg); + final thumbprint = privateKeyJwk.computeThumbprint(); + await _storage.write(key: thumbprint, value: privateKeyJwk.toString()); + return thumbprint; + } + + @override + Future getPublicKey(String keyAlias) async { + final privateKeyJwkStr = await _storage.read(key: keyAlias); + + if (privateKeyJwkStr == null) { + throw Exception('No key found with alias $keyAlias'); + } + + final privateKeyJwk = Jwk.fromJson(json.decode(privateKeyJwkStr)); + return DsaAlgorithms.computePublicKey(privateKeyJwk); + } + + @override + Future sign(String keyAlias, Uint8List payload) async { + final privateKeyJwkStr = await _storage.read(key: keyAlias); + + if (privateKeyJwkStr == null) { + throw Exception('No key found with alias $keyAlias'); + } + + final privateKeyJwk = Jwk.fromJson(json.decode(privateKeyJwkStr)); + return DsaAlgorithms.sign(privateKeyJwk, payload); + } +} diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 32b46b5f..913cae68 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + base32: + dependency: transitive + description: + name: base32 + sha256: ddad4ebfedf93d4500818ed8e61443b734ffe7cf8a45c668c9b34ef6adde02e2 + url: "https://pub.dev" + source: hosted + version: "2.1.3" boolean_selector: dependency: transitive description: @@ -41,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: @@ -82,10 +98,10 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" + sha256: "09f64db63fee3b2ab8b9038a1346be7d8986977fae3fec601275bf32455ccfc0" url: "https://pub.dev" source: hosted - version: "0.20.3" + version: "0.20.4" flutter_lints: dependency: "direct dev" description: @@ -301,6 +317,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.7" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" riverpod: dependency: transitive description: @@ -359,7 +383,7 @@ packages: description: path: "." ref: main - resolved-ref: "7ba39caf5a950196abfe881a0ffee307aa79c2c2" + resolved-ref: "16e4c095efa420e770a0ee32663f1f52dcfce095" url: "https://github.com/TBD54566975/tbdex-dart.git" source: git version: "0.1.0"