fix(app): nothing secret is copied
The reading of the full reading's fixes found that copied recovery codes and authenticator keys still reached clipboard histories (Windows, KDE's Klipper, Gboard, Samsung's) and cloud clipboards, as no Flutter engine marks a copy as sensitive, and that on Android an app in the background can't empty the clipboard again, so the minute's clearing often never happened. As the developer decided: recovery codes are saved, like the phrase; an authenticator's key is scanned as a QR code, opened in the authenticator app, or typed from the screen. copySecret and its texts go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8c910f8cfc
commit
999877d95c
1 changed files with 59 additions and 150 deletions
|
|
@ -0,0 +1,104 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
void main() {
|
||||
/// What the system's clipboard holds.
|
||||
String? clipboard;
|
||||
|
||||
/// Stands in for the system's clipboard for [tester].
|
||||
void mockClipboard(WidgetTester tester) {
|
||||
clipboard = null;
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
(call) async => switch (call.method) {
|
||||
'Clipboard.setData' => () {
|
||||
clipboard = (call.arguments as Map)['text'] as String?;
|
||||
return null;
|
||||
}(),
|
||||
'Clipboard.getData' => {'text': clipboard},
|
||||
_ => null,
|
||||
},
|
||||
);
|
||||
addTearDown(
|
||||
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pumpView(WidgetTester tester, Widget view) async {
|
||||
tester.view.physicalSize = const Size(412, 1400);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(
|
||||
AppScope(
|
||||
dependencies: TestAppDependencies(gateway: FakeAuthGateway()),
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: testLocalizationsDelegates,
|
||||
supportedLocales: HalkyonAppLocalizations.supportedLocales,
|
||||
home: Scaffold(body: SingleChildScrollView(child: view)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Nothing secret is copied (the reading of the fixes): no app can mark
|
||||
// a copy as sensitive through Flutter, so clipboard histories (Windows,
|
||||
// KDE, Gboard, Samsung's) and cloud clipboards would keep it, and on
|
||||
// Android an app in the background can't empty the clipboard again.
|
||||
|
||||
testWidgets('the recovery phrase is saved, never copied', (tester) async {
|
||||
mockClipboard(tester);
|
||||
await pumpView(
|
||||
tester,
|
||||
const RecoveryPhraseView(
|
||||
phrase:
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon '
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon '
|
||||
'abandon abandon abandon abandon abandon abandon abandon art',
|
||||
),
|
||||
);
|
||||
expect(find.text('Copy'), findsNothing);
|
||||
expect(find.text('Save as a file'), findsOneWidget);
|
||||
expect(find.byType(SelectableText), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('recovery codes are saved, never copied', (tester) async {
|
||||
mockClipboard(tester);
|
||||
await pumpView(
|
||||
tester,
|
||||
const RecoveryCodesView(codes: ['AAAA-BBBB', 'CCCC-DDDD']),
|
||||
);
|
||||
expect(find.text('Copy'), findsNothing);
|
||||
expect(find.byTooltip('Copy'), findsNothing);
|
||||
expect(find.text('Save as a file'), findsOneWidget);
|
||||
expect(find.byType(SelectableText), findsNothing);
|
||||
expect(clipboard, isNull);
|
||||
});
|
||||
|
||||
testWidgets("an authenticator's key is scanned, opened or typed from the "
|
||||
'screen, never copied', (tester) async {
|
||||
mockClipboard(tester);
|
||||
final code = TextEditingController();
|
||||
addTearDown(code.dispose);
|
||||
await pumpView(
|
||||
tester,
|
||||
AuthenticatorSetupView(
|
||||
setup: const AuthenticatorSetup(
|
||||
id: 'a',
|
||||
uri: 'otpauth://totp/x:anna?secret=JBSWY3DPEHPK3PXP',
|
||||
),
|
||||
code: code,
|
||||
),
|
||||
);
|
||||
expect(find.text('JBSW Y3DP EHPK 3PXP'), findsOneWidget);
|
||||
expect(find.byTooltip('Copy'), findsNothing);
|
||||
expect(find.text('Copy'), findsNothing);
|
||||
expect(find.byType(SelectableText), findsNothing);
|
||||
expect(clipboard, isNull);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue