From 9c542984e0b0e58194e6ca67413cb3d426a1ff63 Mon Sep 17 00:00:00 2001 From: ice-endymion <188437551+ice-endymion@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:35:30 +0100 Subject: [PATCH] feat: reauth after delegation available (#680) ## Description Reauth relays after user delegation is send. ## Type of Change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [ ] Refactoring - [ ] Documentation - [ ] Chore --- .../providers/ion_connect_notifier.c.dart | 24 ++++++++--- .../mixins/relay_delegation_mixin.dart | 37 +++++++++++++++++ .../providers/mixins/relay_init_mixin.dart | 11 +---- .../providers/relays_provider.c.dart | 6 ++- .../providers/user_delegation_provider.c.dart | 41 +++++++++++++++---- 5 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 lib/app/features/ion_connect/providers/mixins/relay_delegation_mixin.dart diff --git a/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart b/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart index 66b2ac091..65abcac4b 100644 --- a/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart +++ b/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart @@ -17,6 +17,7 @@ import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart' import 'package:ion/app/features/ion_connect/providers/ion_connect_event_parser.c.dart'; import 'package:ion/app/features/ion_connect/providers/ion_connect_event_signer_provider.c.dart'; import 'package:ion/app/features/ion_connect/providers/relay_creation_provider.c.dart'; +import 'package:ion/app/features/user/providers/user_delegation_provider.c.dart'; import 'package:ion/app/services/ion_identity/ion_identity_provider.c.dart'; import 'package:ion/app/services/logger/logger.dart'; import 'package:ion/app/utils/retry.dart'; @@ -45,7 +46,7 @@ class IonConnectNotifier extends _$IonConnectNotifier { .getRelay(actionSource, dislikedUrls: dislikedRelaysUrls); if (_isAuthRequired(error)) { - await sendAuthEvent(relay!); + await _sendAuthEvent(relay!); } await relay!.sendEvents(events); @@ -78,7 +79,20 @@ class IonConnectNotifier extends _$IonConnectNotifier { return result?.elementAtOrNull(0); } - Future sendAuthEvent(IonConnectRelay relay) async { + Future initRelayAuth(IonConnectRelay relay) async { + final signedAuthEvent = await createAuthEvent( + challenge: 'init', + relayUrl: Uri.parse(relay.url).toString(), + ); + + await sendEvent( + signedAuthEvent, + relay: relay, + cache: false, + ); + } + + Future _sendAuthEvent(IonConnectRelay relay) async { final challenge = ref.read(authChallengeProvider(relay.url)); if (challenge == null || challenge.isEmpty) throw AuthChallengeIsEmptyException(); @@ -113,7 +127,8 @@ class IonConnectNotifier extends _$IonConnectNotifier { relay: relayUrl, ); - return sign(authEvent); + final delegation = await ref.read(currentUserCachedDelegationProvider.future); + return sign(authEvent, includeMasterPubkey: delegation != null); } Future?> sendEntitiesData( @@ -151,8 +166,7 @@ class IonConnectNotifier extends _$IonConnectNotifier { if (_isAuthRequired(error)) { try { - // TODO: handle multiple auth requests to one connectoon properly - await sendAuthEvent(relay!); + await _sendAuthEvent(relay!); } catch (error, stackTrace) { Logger.log('Send auth exception', error: error, stackTrace: stackTrace); } diff --git a/lib/app/features/ion_connect/providers/mixins/relay_delegation_mixin.dart b/lib/app/features/ion_connect/providers/mixins/relay_delegation_mixin.dart new file mode 100644 index 000000000..4aba132e3 --- /dev/null +++ b/lib/app/features/ion_connect/providers/mixins/relay_delegation_mixin.dart @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: ice License 1.0 + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ion/app/features/ion_connect/ion_connect.dart'; +import 'package:ion/app/features/ion_connect/providers/ion_connect_event_signer_provider.c.dart'; +import 'package:ion/app/features/ion_connect/providers/ion_connect_notifier.c.dart'; +import 'package:ion/app/features/user/model/user_delegation.c.dart'; +import 'package:ion/app/features/user/providers/user_delegation_provider.c.dart'; + +mixin RelayDelegationMixin { + void initializeDelegationListener(IonConnectRelay relay, Ref ref) { + ref.listen( + currentUserCachedDelegationProvider, + (previous, next) => _handleDelegationChange(previous, next, relay, ref), + ); + } + + void _handleDelegationChange( + AsyncValue? previous, + AsyncValue next, + IonConnectRelay relay, + Ref ref, + ) { + if (previous?.value == null && next.value != null) { + ref.read(currentUserIonConnectEventSignerProvider.future).then((eventSigner) { + if (eventSigner != null) { + final hasDelegate = + next.value?.data.hasDelegateFor(pubkey: eventSigner.publicKey) ?? false; + + if (hasDelegate) { + ref.read(ionConnectNotifierProvider.notifier).initRelayAuth(relay); + } + } + }); + } + } +} diff --git a/lib/app/features/ion_connect/providers/mixins/relay_init_mixin.dart b/lib/app/features/ion_connect/providers/mixins/relay_init_mixin.dart index a19851f03..0db7bd9c5 100644 --- a/lib/app/features/ion_connect/providers/mixins/relay_init_mixin.dart +++ b/lib/app/features/ion_connect/providers/mixins/relay_init_mixin.dart @@ -21,16 +21,7 @@ mixin RelayInitMixin { _initCompleters[relay.url] = completer; try { - final signedAuthEvent = await ref.read(ionConnectNotifierProvider.notifier).createAuthEvent( - challenge: 'init', - relayUrl: Uri.parse(relay.url).toString(), - ); - - await ref.read(ionConnectNotifierProvider.notifier).sendEvent( - signedAuthEvent, - relay: relay, - cache: false, - ); + await ref.read(ionConnectNotifierProvider.notifier).initRelayAuth(relay); _subscriptions[relay.url] = relay.onClose.listen( (url) { diff --git a/lib/app/features/ion_connect/providers/relays_provider.c.dart b/lib/app/features/ion_connect/providers/relays_provider.c.dart index 8d673761d..777d19fbb 100644 --- a/lib/app/features/ion_connect/providers/relays_provider.c.dart +++ b/lib/app/features/ion_connect/providers/relays_provider.c.dart @@ -2,6 +2,7 @@ import 'package:ion/app/features/ion_connect/ion_connect.dart'; import 'package:ion/app/features/ion_connect/providers/mixins/relay_auth_mixin.dart'; +import 'package:ion/app/features/ion_connect/providers/mixins/relay_delegation_mixin.dart'; import 'package:ion/app/features/ion_connect/providers/mixins/relay_init_mixin.dart'; import 'package:ion/app/features/ion_connect/providers/mixins/relay_timer_mixin.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -11,13 +12,16 @@ part 'relays_provider.c.g.dart'; typedef RelaysState = Map; @Riverpod(keepAlive: true) -class Relay extends _$Relay with RelayTimerMixin, RelayAuthMixin, RelayInitMixin { +class Relay extends _$Relay + with RelayTimerMixin, RelayAuthMixin, RelayInitMixin, RelayDelegationMixin { @override Future build(String url, {bool anonymous = false}) async { final relay = await IonConnectRelay.connect(url); initializeRelayTimer(relay, ref); + if (!anonymous) { + initializeDelegationListener(relay, ref); initializeAuthMessageListener(relay, ref); await initRelay(relay, ref); } diff --git a/lib/app/features/user/providers/user_delegation_provider.c.dart b/lib/app/features/user/providers/user_delegation_provider.c.dart index a75da1ed6..a538ea3d2 100644 --- a/lib/app/features/user/providers/user_delegation_provider.c.dart +++ b/lib/app/features/user/providers/user_delegation_provider.c.dart @@ -15,16 +15,10 @@ part 'user_delegation_provider.c.g.dart'; @Riverpod(keepAlive: true) Future userDelegation(Ref ref, String pubkey) async { - final userDelegation = ref.watch( - ionConnectCacheProvider.select( - cacheSelector( - CacheableEntity.cacheKeyBuilder( - eventReference: - ReplaceableEventReference(pubkey: pubkey, kind: UserDelegationEntity.kind), - ), - ), - ), + final userDelegation = await ref.watch( + cachedUserDelegationProvider(pubkey).future, ); + if (userDelegation != null) { return userDelegation; } @@ -40,6 +34,24 @@ Future userDelegation(Ref ref, String pubkey) async { ); } +@Riverpod(keepAlive: true) +Future cachedUserDelegation(Ref ref, String pubkey) async { + final userDelegation = ref.watch( + ionConnectCacheProvider.select( + cacheSelector( + CacheableEntity.cacheKeyBuilder( + eventReference: ReplaceableEventReference( + pubkey: pubkey, + kind: UserDelegationEntity.kind, + ), + ), + ), + ), + ); + + return userDelegation; +} + @Riverpod(keepAlive: true) Future currentUserDelegation(Ref ref) async { final mainWallet = await ref.watch(mainWalletProvider.future); @@ -51,6 +63,17 @@ Future currentUserDelegation(Ref ref) async { } } +@Riverpod(keepAlive: true) +Future currentUserCachedDelegation(Ref ref) async { + final mainWallet = await ref.watch(mainWalletProvider.future); + + try { + return await ref.watch(cachedUserDelegationProvider(mainWallet.signingKey.publicKey).future); + } on UserRelaysNotFoundException catch (_) { + return null; + } +} + @riverpod class UserDelegationManager extends _$UserDelegationManager { @override