git / moonbite-network / mobile/lib/wallet/authenticator.dart · raw

import 'package:local_auth/local_auth.dart';/// Thrown when the user cannot prove presence (biometric / device credential)/// before a fund-moving action. Callers must treat this as a hard stop: no/// transaction is built, signed, or broadcast.class AuthenticationException implements Exception {  final String message;  const AuthenticationException(this.message);  @override  String toString() => 'AuthenticationException: $message';}/// Gates sensitive, irreversible actions (spending) behind a local/// presence check. Abstracted so tests can inject a deterministic stub/// instead of touching the platform biometric channel.abstract class Authenticator {  /// Returns true only if the user actively confirmed their presence.  Future<bool> authenticate(String reason);}/// Real implementation backed by the OS keyguard: biometrics when enrolled,/// otherwise the device PIN/pattern/passcode (biometricOnly: false).////// If the device has no secure lock configured at all, there is nothing to/// gate against; we fail open so the wallet stays usable rather than bricking/// every spend. On a device that *does* support auth, a cancelled or failed/// prompt returns false and the caller aborts.class LocalAuthenticator implements Authenticator {  final LocalAuthentication _auth;  LocalAuthenticator({LocalAuthentication? auth})      : _auth = auth ?? LocalAuthentication();  @override  Future<bool> authenticate(String reason) async {    final supported = await _auth.isDeviceSupported();    if (!supported) return true;    try {      return await _auth.authenticate(        localizedReason: reason,        options: const AuthenticationOptions(          stickyAuth: true,          biometricOnly: false,        ),      );    } catch (_) {      // Any platform error (no enrolment, locked out, channel failure) is      // treated as "not authenticated" — fail closed for the spend.      return false;    }  }}