diff --git a/packages/ndk/lib/domain_layer/usecases/relay_manager.dart b/packages/ndk/lib/domain_layer/usecases/relay_manager.dart index 13faa3dd0..ff707a9dc 100644 --- a/packages/ndk/lib/domain_layer/usecases/relay_manager.dart +++ b/packages/ndk/lib/domain_layer/usecases/relay_manager.dart @@ -385,13 +385,15 @@ class RelayManager { // nip 42 used to send authentication challenges final challenge = eventJson[1]; Logger.log.d("AUTH: ${challenge}"); - if (signer != null) { + if (signer != null && signer!.canSign()) { final auth = AuthEvent(pubKey: signer!.getPublicKey(), tags: [ ["relay", relayConnectivity.url], ["challenge", challenge] ]); signer!.sign(auth); send(relayConnectivity, ClientMsg(ClientMsgType.AUTH, event: auth)); + } else { + Logger.log.w("Received an AUTH challenge but don't have a signer configured"); } return; } diff --git a/packages/ndk/test/mocks/mock_relay.dart b/packages/ndk/test/mocks/mock_relay.dart index 6018d3df6..2409cadd3 100644 --- a/packages/ndk/test/mocks/mock_relay.dart +++ b/packages/ndk/test/mocks/mock_relay.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:ndk/entities.dart'; +import 'package:ndk/shared/nips/nip01/helpers.dart'; import 'package:ndk/shared/nips/nip01/key_pair.dart'; class MockRelay { @@ -14,6 +15,7 @@ class MockRelay { Map? nip65s; Map? textNotes; bool signEvents; + bool requireAuthForRequests; static int startPort = 4040; @@ -23,6 +25,7 @@ class MockRelay { required this.name, this.nip65s, this.signEvents = true, + this.requireAuthForRequests = false, int? explicitPort, }) { if (explicitPort != null) { @@ -51,15 +54,32 @@ class MockRelay { this.server = server; var stream = server.transform(WebSocketTransformer()); + String challenge; + bool signedChallenge=false; stream.listen((webSocket) { this.webSocket = webSocket; + if (requireAuthForRequests && !signedChallenge) { + challenge = Helpers.getRandomString(10); + webSocket.add(jsonEncode(["AUTH", challenge])); + } webSocket.listen((message) { if (message == "ping") { webSocket.add("pong"); return; } var eventJson = json.decode(message); + if (eventJson[0] == "AUTH") { + Nip01Event event = Nip01Event.fromJson(eventJson[1]); + // TODO check signature + if (event.) + signedChallenge = true; + print(event); + } + if (requireAuthForRequests && !signedChallenge) { + webSocket.add(jsonEncode(["CLOSED", "sub_1","auth-required: we can't serve requests to unauthenticated users"])); + return; + } if (eventJson[0] == "REQ") { String requestId = eventJson[1]; log('Received: $eventJson'); diff --git a/packages/ndk/test/relays/nip42_test.dart b/packages/ndk/test/relays/nip42_test.dart new file mode 100644 index 000000000..5d4bd3ea3 --- /dev/null +++ b/packages/ndk/test/relays/nip42_test.dart @@ -0,0 +1,87 @@ +// ignore_for_file: avoid_print + +import 'dart:async'; + +import 'package:ndk/data_layer/repositories/nostr_transport/websocket_client_nostr_transport_factory.dart'; +import 'package:ndk/data_layer/repositories/signers/bip340_event_signer.dart'; +import 'package:ndk/domain_layer/entities/global_state.dart'; +import 'package:ndk/domain_layer/usecases/relay_manager.dart'; +import 'package:ndk/entities.dart'; +import 'package:ndk/ndk.dart'; +import 'package:ndk/shared/nips/nip01/bip340.dart'; +import 'package:ndk/shared/nips/nip01/key_pair.dart'; +import 'package:test/test.dart'; + +import '../mocks/mock_relay.dart'; + +void main() async { + group('NIP-42', () { + + KeyPair key1 = Bip340.generatePrivateKey(); + + Map keyNames = { + key1: "key1", + }; + + Nip01Event textNote(KeyPair key2) { + return Nip01Event( + kind: Nip01Event.TEXT_NODE_KIND, + pubKey: key2.publicKey, + content: "some note from key ${keyNames[key1]}", + tags: [], + createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000); + } + + Map key1TextNotes = {key1: textNote(key1)}; + + test('respond to auth challenge', () async { + MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 3900, requireAuthForRequests: true); + await relay1.startServer(textNotes: key1TextNotes); + + final ndk = Ndk( + NdkConfig( + eventVerifier: Bip340EventVerifier(), + eventSigner: Bip340EventSigner( + privateKey: key1.privateKey, + publicKey: key1.publicKey, + ), + cache: MemCacheManager(), + logLevel: Logger.logLevels.trace, + bootstrapRelays: [relay1.url]), + ); + + await Future.delayed(Duration(seconds: 3)); + final response = ndk.requests.query(filters: [ + Filter(kinds: [Nip01Event.TEXT_NODE_KIND], authors: [key1.publicKey]) + ]); + await expectLater(response.stream, emitsInAnyOrder(key1TextNotes.values)); + + // TODO write some events + // TODO do some requests + await ndk.destroy(); + await relay1.stopServer(); + }); + + test("check that relay does not return events if we don't provide a signer", () async { + MockRelay relay1 = MockRelay(name: "relay 1", explicitPort: 3900, requireAuthForRequests: true); + await relay1.startServer(textNotes: key1TextNotes); + + final ndk = Ndk( + NdkConfig( + eventVerifier: Bip340EventVerifier(), + cache: MemCacheManager(), + logLevel: Logger.logLevels.trace, + bootstrapRelays: [relay1.url]), + ); + + await Future.delayed(Duration(seconds: 1)); + final response = ndk.requests.query(filters: [ + Filter(kinds: [Nip01Event.TEXT_NODE_KIND], authors: [key1.publicKey]) + ]); + List events = await response.future; + expect(events, isEmpty); + await ndk.destroy(); + await relay1.stopServer(); + }); + }); +} diff --git a/packages/ndk/test/relays/relay_manager_test.dart b/packages/ndk/test/relays/relay_manager_test.dart index ea12e32a4..9ab026279 100644 --- a/packages/ndk/test/relays/relay_manager_test.dart +++ b/packages/ndk/test/relays/relay_manager_test.dart @@ -3,11 +3,9 @@ import 'dart:async'; import 'package:ndk/data_layer/repositories/nostr_transport/websocket_client_nostr_transport_factory.dart'; -import 'package:ndk/domain_layer/entities/global_state.dart'; import 'package:ndk/domain_layer/usecases/relay_manager.dart'; import 'package:ndk/entities.dart'; import 'package:test/test.dart'; -import 'package:ndk/data_layer/repositories/nostr_transport/websocket_nostr_transport_factory.dart'; import '../mocks/mock_relay.dart'; @@ -38,6 +36,7 @@ void main() async { test('Try to connect to dead relay', () async { RelayManager manager = RelayManager( nostrTransportFactory: webSocketNostrTransportFactory, + bootstrapRelays: [], globalState: GlobalState(), );