diff --git a/lib/utilities/test_monero_node_connection.dart b/lib/utilities/test_monero_node_connection.dart index aef26ba9a..8901d4d2e 100644 --- a/lib/utilities/test_monero_node_connection.dart +++ b/lib/utilities/test_monero_node_connection.dart @@ -12,6 +12,9 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:http/io_client.dart'; +import 'package:monero_rpc/monero_rpc.dart'; +import 'package:digest_auth/digest_auth.dart'; import 'package:socks5_proxy/socks.dart'; import 'package:tor_ffi_plugin/socks_socket.dart'; @@ -27,11 +30,18 @@ class MoneroNodeConnectionResponse { final int? port; final bool success; - MoneroNodeConnectionResponse(this.cert, this.url, this.port, this.success); + MoneroNodeConnectionResponse( + this.cert, + this.url, + this.port, + this.success, + ); } Future testMoneroNodeConnection( Uri uri, + String? username, + String? password, bool allowBadX509Certificate, { required ({ InternetAddress host, @@ -59,36 +69,37 @@ Future testMoneroNodeConnection( await socket.connect(); await socket.connectTo(uri.host, uri.port); - final body = jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - "method": "get_info", - }); - - final request = 'POST /json_rpc HTTP/1.1\r\n' - 'Host: ${uri.host}\r\n' - 'Content-Type: application/json\r\n' - 'Content-Length: ${body.length}\r\n' - '\r\n' - '$body'; - - socket.write(request); - print("Request sent: $request"); - - final buffer = StringBuffer(); - await for (var response in socket.inputStream) { - buffer.write(utf8.decode(response)); - if (buffer.toString().contains("\r\n\r\n")) { - break; + final rawRequest = DaemonRpc.rawRequestRpc(uri, 'get_info', {}); + var response = await socket.send(rawRequest); + // check if we need authentication + String? authenticateHeaderValue; + for (final line in response.split('\r\n')) { + if (line.contains('WWW-authenticate: ')) { + // both the password and username needs to be + if (username == null || password == null) { + // node asking us for authentication, but we don't have any crendentials. + return MoneroNodeConnectionResponse(null, null, null, false); + } + authenticateHeaderValue = + line.replaceFirst('WWW-authenticate: ', '').trim(); } } - - final result = buffer.toString(); - print("Response received: $result"); + // header to authenticate was present, we need to remake the request with digest + if (authenticateHeaderValue != null) { + final digestAuth = DigestAuth(username!, password!); + digestAuth.initFromAuthorizationHeader(authenticateHeaderValue); + + // generate the Authorization header for the second request. + final authHeader = digestAuth.getAuthString('POST', uri.path); + final rawRequestAuthenticated = + DaemonRpc.rawRequestRpc(uri, 'get_info', {}, authHeader); + // resend with an authenticated request + response = await socket.send(rawRequestAuthenticated); + } // Check if the response contains "results" and does not contain "error" final success = - result.contains('"result":') && !result.contains('"error"'); + response.contains('"result":') && !response.contains('"error"'); return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { @@ -124,36 +135,15 @@ Future testMoneroNodeConnection( return false; }; - - final request = await httpClient.postUrl(uri); - - final body = utf8.encode( - jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - "method": "get_info", - }), - ); - - request.headers.add( - 'Content-Length', - body.length.toString(), - preserveHeaderCase: true, - ); - request.headers.set( - 'Content-Type', - 'application/json', - preserveHeaderCase: true, + final daemonRpc = DaemonRpc( + IOClient(httpClient), + '$uri', + username: username, + password: password, ); + final result = await daemonRpc.call('get_info', {}); - request.add(body); - - final response = await request.close(); - final result = await response.transform(utf8.decoder).join(); - // print("HTTP Response: $result"); - - final success = - result.contains('"result":') && !result.contains('"error"'); + final success = result.containsKey('status') && result['status'] == 'OK'; return MoneroNodeConnectionResponse(null, null, null, success); } catch (e, s) { @@ -210,3 +200,18 @@ Future showBadX509CertificateDialog( return result ?? false; } + +extension on SOCKSSocket { + /// write the raw request to the socket and return the response as String + Future send(String rawRequest) async { + write(rawRequest); + final buffer = StringBuffer(); + await for (final response in inputStream) { + buffer.write(utf8.decode(response)); + if (buffer.toString().contains("\r\n\r\n")) { + break; + } + } + return buffer.toString(); + } +} diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 127702b14..9169cb636 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -38,6 +38,8 @@ Future _xmrHelper( final data = nodeFormData; final url = data.host!; final port = data.port; + final username = data.login; + final password = data.password; final uri = Uri.parse(url); @@ -51,6 +53,8 @@ Future _xmrHelper( final response = await testMoneroNodeConnection( Uri.parse(uriString), + username, + password, false, proxyInfo: proxyInfo, ).timeout(Duration(seconds: proxyInfo != null ? 30 : 10)); @@ -67,6 +71,8 @@ Future _xmrHelper( if (shouldAllowBadCert) { final response = await testMoneroNodeConnection( Uri.parse(uriString), + username, + password, true, proxyInfo: proxyInfo, ); diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e44cdcab4..e052525ae 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -204,6 +204,8 @@ dependencies: cbor: ^6.3.3 cs_monero: 1.0.0-pre.1 cs_monero_flutter_libs: 1.0.0-pre.0 + monero_rpc: ^2.0.0 + digest_auth: ^1.0.1 dev_dependencies: flutter_test: