mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
205 lines
5.5 KiB
Dart
205 lines
5.5 KiB
Dart
import 'package:buzz/shared/deeplink/deep_link.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
_inviteTests();
|
|
|
|
group('parseMessageDeepLink', () {
|
|
test('parses channel and id', () {
|
|
final link = parseMessageDeepLink(
|
|
Uri.parse('buzz://message?channel=d14cd131&id=abc123'),
|
|
);
|
|
expect(
|
|
link,
|
|
const MessageDeepLink(channelId: 'd14cd131', messageId: 'abc123'),
|
|
);
|
|
});
|
|
|
|
test('parses optional thread param', () {
|
|
final link = parseMessageDeepLink(
|
|
Uri.parse('buzz://message?channel=d14cd131&id=abc123&thread=root99'),
|
|
);
|
|
expect(link?.threadRootId, 'root99');
|
|
});
|
|
|
|
test('treats empty thread as absent', () {
|
|
final link = parseMessageDeepLink(
|
|
Uri.parse('buzz://message?channel=d14cd131&id=abc123&thread='),
|
|
);
|
|
expect(link, isNotNull);
|
|
expect(link?.threadRootId, isNull);
|
|
});
|
|
|
|
test('rejects missing channel', () {
|
|
expect(parseMessageDeepLink(Uri.parse('buzz://message?id=abc')), isNull);
|
|
});
|
|
|
|
test('rejects empty channel', () {
|
|
expect(
|
|
parseMessageDeepLink(Uri.parse('buzz://message?channel=&id=abc')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects missing id', () {
|
|
expect(
|
|
parseMessageDeepLink(Uri.parse('buzz://message?channel=d14cd131')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects non-buzz scheme', () {
|
|
expect(
|
|
parseMessageDeepLink(Uri.parse('https://message?channel=a&id=b')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects non-message host (connect is desktop-only)', () {
|
|
expect(
|
|
parseMessageDeepLink(Uri.parse('buzz://connect?relay=wss://x')),
|
|
isNull,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
void _inviteTests() {
|
|
group('parseInviteDeepLink', () {
|
|
test('parses canonical HTTPS invite URL', () {
|
|
final link = parseInviteDeepLink(
|
|
Uri.parse('https://relay.example.com/invite/abc123'),
|
|
);
|
|
expect(
|
|
link,
|
|
const InviteDeepLink(
|
|
relayUrl: 'wss://relay.example.com',
|
|
code: 'abc123',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('parses HTTP invite URL for local/dev relays', () {
|
|
final link = parseInviteDeepLink(
|
|
Uri.parse('http://localhost:3000/invite/dev-code'),
|
|
);
|
|
expect(
|
|
link,
|
|
const InviteDeepLink(relayUrl: 'ws://localhost:3000', code: 'dev-code'),
|
|
);
|
|
});
|
|
|
|
test('parses buzz join handoff link', () {
|
|
final link = parseInviteDeepLink(
|
|
Uri.parse(
|
|
'buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=abc123',
|
|
),
|
|
);
|
|
expect(
|
|
link,
|
|
const InviteDeepLink(
|
|
relayUrl: 'wss://relay.example.com',
|
|
code: 'abc123',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('preserves policy receipt in buzz join handoff', () {
|
|
final link = parseInviteDeepLink(
|
|
Uri.parse(
|
|
'buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=abc123&policy_receipt=receipt.value',
|
|
),
|
|
);
|
|
expect(
|
|
link,
|
|
const InviteDeepLink(
|
|
relayUrl: 'wss://relay.example.com',
|
|
code: 'abc123',
|
|
policyReceipt: 'receipt.value',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('rejects non-invite HTTPS paths', () {
|
|
expect(
|
|
parseInviteDeepLink(Uri.parse('https://relay.example.com/api/invites')),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(Uri.parse('https://relay.example.com/invite/')),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(Uri.parse('https://relay.example.com/invite/a/b')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects credentials and fragments', () {
|
|
expect(
|
|
parseInviteDeepLink(
|
|
Uri.parse('https://user:pass@relay.example.com/invite/abc'),
|
|
),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(
|
|
Uri.parse('https://relay.example.com/invite/abc#x'),
|
|
),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(
|
|
Uri.parse(
|
|
'buzz://join?relay=wss%3A%2F%2Fuser%3Apass%40relay.example.com&code=abc',
|
|
),
|
|
),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects buzz join without websocket relay or code', () {
|
|
expect(
|
|
parseInviteDeepLink(
|
|
Uri.parse('buzz://join?relay=https://relay.example.com&code=abc'),
|
|
),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(
|
|
Uri.parse('buzz://join?relay=wss://relay.example.com'),
|
|
),
|
|
isNull,
|
|
);
|
|
expect(
|
|
parseInviteDeepLink(Uri.parse('buzz://connect?relay=wss://x')),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('rejects buzz join with dangerous relay schemes', () {
|
|
// The `relay=` param is an allowlist — only `ws` / `wss` are safe to
|
|
// hand to a Nostr relay session. Anything else must be dropped by the
|
|
// parser so a hostile QR / share link can't smuggle a browser scheme
|
|
// (`javascript:`, `data:`), a local resource (`file:`), or an
|
|
// unrelated transport (`ftp:`, `chrome:`) into the join flow.
|
|
for (final hostile in [
|
|
'javascript:alert(1)',
|
|
'data:text/html,evil',
|
|
'file:///etc/passwd',
|
|
'ftp://relay.example.com',
|
|
'chrome://settings',
|
|
'about:blank',
|
|
'ssh://relay.example.com',
|
|
]) {
|
|
final encoded = Uri.encodeQueryComponent(hostile);
|
|
expect(
|
|
parseInviteDeepLink(Uri.parse('buzz://join?relay=$encoded&code=abc')),
|
|
isNull,
|
|
reason: 'must reject relay scheme in $hostile',
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|