diff --git a/apps/desktop/lib/src/shell/party_strip.dart b/apps/desktop/lib/src/shell/party_strip.dart index 87ae2f3b..19d8cc17 100644 --- a/apps/desktop/lib/src/shell/party_strip.dart +++ b/apps/desktop/lib/src/shell/party_strip.dart @@ -508,7 +508,10 @@ class MembersCard extends StatelessWidget { alignment: Alignment.centerLeft, child: TextButton.icon( key: const ValueKey('copy-invite'), - onPressed: () => copyToClipboard(context, friendInviteLink(address, nav.serverLabel.peek()), + onPressed: () => copyToClipboard( + context, + friendInviteLink(address, nav.serverLabel.peek(), + secret: AppScope.of(context).connection.params?.secret ?? ''), done: 'Invite link copied'), icon: const Icon(Icons.copy, size: 16), label: const Text('Copy invite link'), @@ -627,10 +630,11 @@ bool versionDiffers(String version, {String own = appVersion}) => version.isNotEmpty && own != '0.0.0-dev' && version != '0.0.0-dev' && version != own; /// The invite link to a friend's server you are in ([address] as dialled, -/// "host:port"; never its password). -String friendInviteLink(String address, String name) { +/// "host:port"), with the password you joined with: the link is the key, +/// as for your own server. +String friendInviteLink(String address, String name, {String secret = ''}) { final a = addressIn(address); final port = a == null ? null : int.tryParse(a.port); if (a == null || port == null) return address; - return InviteLink(host: a.host, port: port, name: name).uri; + return InviteLink(host: a.host, port: port, name: name, secret: secret).uri; } diff --git a/apps/desktop/test/invite_links_test.dart b/apps/desktop/test/invite_links_test.dart index f493b5cf..f1f83e0b 100644 --- a/apps/desktop/test/invite_links_test.dart +++ b/apps/desktop/test/invite_links_test.dart @@ -11,6 +11,7 @@ import 'package:starsync/src/session/invite.dart'; import 'package:starsync/src/session/join_offers.dart'; import 'package:starsync/src/session/navigation.dart' show Place; import 'package:starsync/src/session/public_address.dart'; +import 'package:starsync/src/shell/party_strip.dart' show friendInviteLink; import 'package:starsync/src/shell/join_offer_card.dart'; import 'package:starsync/src/state/connect_prefs.dart' show RecentServer; @@ -33,6 +34,13 @@ String health({String name = "Bob's server", String version = '1.3.2', int viewe final _graph = testGraph(healthGet: (uri) async => health()); void main() { + test('a friend\'s server: the copied link carries the password you joined with', () { + final link = friendInviteLink('example.org:6969', "Bob's server", secret: 'hunter2'); + final back = InviteLink.parse(link)!; + expect([back.host, back.port, back.secret], ['example.org', 6969, 'hunter2']); + expect(friendInviteLink('example.org:6969', 'Bob'), isNot(contains('pw='))); + }); + group('invite link', () { test('round trip: address, name and password, which never shows in toString', () { const link = InviteLink(host: '203.0.113.7', port: 9000, name: "Bob's server & co", secret: 'p@ss w&rd=1'); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index eafbb1fc..a4c9f999 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2011,8 +2011,9 @@ panel) and chat messages (Copy message, and a link in it). computer; no internet address found: the link works on the home network only); "Other addresses" (folded, only with more than one) lists each address of `YourServer.reach` (Internet, Home network, VPN) to pick - what Copy copies; on a friend's, "Copy invite link" (the dialled address - and its name, never a password)). + what Copy copies; on a friend's, "Copy invite link" (the dialled address, + its name and the password you joined with: the link is the key there + too))). Empty strip space is the window drag area; the window buttons sit at its end. - **Avatars** (`avatar.dart`): `StatusAvatar` draws a ring outside the