fix(app): only an otpauth URI is shown as a QR code or opened
The instance sends the URI an authenticator is set up from; one that isn't otpauth: (a web page, an Android intent) was drawn as a QR code and handed to the system to open. Now it is neither; the key stays to type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c9904de31d
commit
5270ce6775
3 changed files with 64 additions and 3 deletions
|
|
@ -18,6 +18,12 @@ final class const AuthenticatorSetup({
|
|||
required final String id,
|
||||
required final String uri,
|
||||
}) with Equatable {
|
||||
/// Whether [uri] is what an authenticator app opens (`otpauth:`): only
|
||||
/// then is it shown as a QR code or handed to the system to open, as the
|
||||
/// instance could send any address (a web page, an intent).
|
||||
bool get isAuthenticatorUri =>
|
||||
Uri.tryParse(uri)?.isScheme('otpauth') ?? false;
|
||||
|
||||
/// The secret the URI holds, Base32, in groups of four to type by hand.
|
||||
String get secret {
|
||||
final raw = Uri.tryParse(uri)?.queryParameters['secret'] ?? '';
|
||||
|
|
|
|||
|
|
@ -550,13 +550,17 @@ class const AuthenticatorSetupView({
|
|||
Widget build(BuildContext context) {
|
||||
final l10n = HalkyonAppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final phone = Platform.isAndroid || Platform.isIOS;
|
||||
// Only an otpauth URI is shown as a QR code or opened ([AuthenticatorSetup.isAuthenticatorUri]).
|
||||
final opens = setup.isAuthenticatorUri;
|
||||
final phone = opens && (Platform.isAndroid || Platform.isIOS);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SheetIntro(l10n.passwordAuthenticatorIntro),
|
||||
Center(child: QrCodeView(data: setup.uri)),
|
||||
const SizedBox(height: 12),
|
||||
if (opens) ...[
|
||||
Center(child: QrCodeView(data: setup.uri)),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
Text(l10n.passwordAuthenticatorKey, style: theme.textTheme.labelLarge),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -57,4 +57,55 @@ void main() {
|
|||
);
|
||||
expectNothingLearnt(tester, "the phrase's words");
|
||||
});
|
||||
|
||||
test("only an otpauth URI is an authenticator's to open", () {
|
||||
expect(
|
||||
const AuthenticatorSetup(
|
||||
id: 'a',
|
||||
uri: 'otpauth://totp/x:anna?secret=JBSWY3DPEHPK3PXP',
|
||||
).isAuthenticatorUri,
|
||||
isTrue,
|
||||
);
|
||||
for (final uri in [
|
||||
'https://evil.example/otpauth',
|
||||
'intent://scan#Intent;scheme=otpauth;end',
|
||||
'tel:+4930123',
|
||||
'',
|
||||
]) {
|
||||
expect(
|
||||
AuthenticatorSetup(id: 'a', uri: uri).isAuthenticatorUri,
|
||||
isFalse,
|
||||
reason: uri,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('what an instance sends that is no otpauth URI is never shown '
|
||||
'as a QR code', (tester) async {
|
||||
final code = TextEditingController();
|
||||
addTearDown(code.dispose);
|
||||
await pump(
|
||||
tester,
|
||||
AuthenticatorSetupView(
|
||||
setup: const AuthenticatorSetup(
|
||||
id: 'a',
|
||||
uri: 'https://evil.example/?secret=JBSWY3DPEHPK3PXP',
|
||||
),
|
||||
code: code,
|
||||
),
|
||||
);
|
||||
expect(find.byType(QrCodeView), findsNothing);
|
||||
|
||||
await pump(
|
||||
tester,
|
||||
AuthenticatorSetupView(
|
||||
setup: const AuthenticatorSetup(
|
||||
id: 'a',
|
||||
uri: 'otpauth://totp/x:anna?secret=JBSWY3DPEHPK3PXP',
|
||||
),
|
||||
code: code,
|
||||
),
|
||||
);
|
||||
expect(find.byType(QrCodeView), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue