diff --git a/auth/lib/main.dart b/auth/lib/main.dart index b0adde52fa..54f8da92aa 100644 --- a/auth/lib/main.dart +++ b/auth/lib/main.dart @@ -14,7 +14,6 @@ import 'package:ente_auth/services/billing_service.dart'; import 'package:ente_auth/services/notification_service.dart'; import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/services/update_service.dart'; -import 'package:ente_auth/services/user_remote_flag_service.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/services/window_listener_service.dart'; import 'package:ente_auth/store/code_display_store.dart'; @@ -156,7 +155,6 @@ Future _init(bool bool, {String? via}) async { await Configuration.instance.init(); await Network.instance.init(); await UserService.instance.init(); - await UserRemoteFlagService.instance.init(); await AuthenticatorService.instance.init(); await BillingService.instance.init(); await NotificationService.instance.init(); diff --git a/auth/lib/services/user_remote_flag_service.dart b/auth/lib/services/user_remote_flag_service.dart deleted file mode 100644 index 74deae5843..0000000000 --- a/auth/lib/services/user_remote_flag_service.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:collection/collection.dart'; -import 'package:dio/dio.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; -import 'package:ente_auth/core/network.dart'; -import 'package:ente_auth/events/notification_event.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:logging/logging.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class UserRemoteFlagService { - final _dio = Network.instance.getDio(); - final _logger = Logger((UserRemoteFlagService).toString()); - final _config = Configuration.instance; - late SharedPreferences _prefs; - - UserRemoteFlagService._privateConstructor(); - - static final UserRemoteFlagService instance = - UserRemoteFlagService._privateConstructor(); - - static const String recoveryVerificationFlag = "recoveryKeyVerified"; - static const String needRecoveryKeyVerification = - "needRecoveryKeyVerification"; - - Future init() async { - _prefs = await SharedPreferences.getInstance(); - } - - bool shouldShowRecoveryVerification() { - if (!_prefs.containsKey(needRecoveryKeyVerification)) { - // fetch the status from remote - unawaited(_refreshRecoveryVerificationFlag()); - return false; - } else { - final bool shouldShow = _prefs.getBool(needRecoveryKeyVerification)!; - if (shouldShow) { - // refresh the status to check if user marked it as done on another device - unawaited(_refreshRecoveryVerificationFlag()); - } - return shouldShow; - } - } - - // markRecoveryVerificationAsDone is used to track if user has verified their - // recovery key in the past or not. This helps in avoid showing the same - // prompt to the user on re-install or signing into a different device - Future markRecoveryVerificationAsDone() async { - await _updateKeyValue(recoveryVerificationFlag, true.toString()); - await _prefs.setBool(needRecoveryKeyVerification, false); - } - - Future _refreshRecoveryVerificationFlag() async { - _logger.finest('refresh recovery key verification flag'); - final remoteStatusValue = - await _getValue(recoveryVerificationFlag, "false"); - final bool isNeedVerificationFlagSet = - _prefs.containsKey(needRecoveryKeyVerification); - if (remoteStatusValue.toLowerCase() == "true") { - await _prefs.setBool(needRecoveryKeyVerification, false); - // If the user verified on different device, then we should refresh - // the UI to dismiss the Notification. - if (isNeedVerificationFlagSet) { - Bus.instance.fire(NotificationEvent()); - } - } else if (!isNeedVerificationFlagSet) { - // Verification is not done yet as remoteStatus is false and local flag to - // show notification isn't set. Set the flag to true if any active - // session is older than 1 day. - final activeSessions = await UserService.instance.getActiveSessions(); - final int microSecondsInADay = const Duration(days: 1).inMicroseconds; - final bool anyActiveSessionOlderThanADay = - activeSessions.sessions.firstWhereOrNull( - (e) => - (e.creationTime + microSecondsInADay) < - DateTime.now().microsecondsSinceEpoch, - ) != - null; - if (anyActiveSessionOlderThanADay) { - await _prefs.setBool(needRecoveryKeyVerification, true); - Bus.instance.fire(NotificationEvent()); - } else { - // continue defaulting to no verification prompt - _logger.finest('No active session older than 1 day'); - } - } - } - - Future _getValue(String key, String? defaultValue) async { - try { - final Map queryParams = {"key": key}; - if (defaultValue != null) { - queryParams["defaultValue"] = defaultValue; - } - final response = await _dio.get( - "${_config.getHttpEndpoint()}/remote-store", - queryParameters: queryParams, - options: Options( - headers: { - "X-Auth-Token": _config.getToken(), - }, - ), - ); - if (response.statusCode != HttpStatus.ok) { - throw Exception("Unexpected status code ${response.statusCode}"); - } - return response.data["value"]; - } catch (e) { - _logger.info("Error while fetching bool status for $key", e); - rethrow; - } - } - - // _setBooleanFlag sets the corresponding flag on remote - // to mark recovery as completed - Future _updateKeyValue(String key, String value) async { - try { - final response = await _dio.post( - "${_config.getHttpEndpoint()}/remote-store/update", - data: { - "key": key, - "value": value, - }, - options: Options( - headers: { - "X-Auth-Token": _config.getToken(), - }, - ), - ); - if (response.statusCode != HttpStatus.ok) { - throw Exception("Unexpected state"); - } - } catch (e) { - _logger.warning("Failed to set flag for $key", e); - rethrow; - } - } -} diff --git a/auth/lib/ui/account/verify_recovery_page.dart b/auth/lib/ui/account/verify_recovery_page.dart deleted file mode 100644 index bb4ebb068a..0000000000 --- a/auth/lib/ui/account/verify_recovery_page.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:bip39/bip39.dart' as bip39; -import 'package:dio/dio.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/services/user_remote_flag_service.dart'; -import 'package:ente_auth/ui/account/recovery_key_page.dart'; -import 'package:ente_auth/ui/common/gradient_button.dart'; -import 'package:ente_auth/ui/components/buttons/button_widget.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/navigation_util.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:ente_crypto_dart/ente_crypto_dart.dart'; -import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; - -class VerifyRecoveryPage extends StatefulWidget { - const VerifyRecoveryPage({super.key}); - - @override - State createState() => _VerifyRecoveryPageState(); -} - -class _VerifyRecoveryPageState extends State { - final _recoveryKey = TextEditingController(); - final Logger _logger = Logger((_VerifyRecoveryPageState).toString()); - - void _verifyRecoveryKey() async { - final dialog = - createProgressDialog(context, context.l10n.verifyingRecoveryKey); - await dialog.show(); - try { - final String inputKey = _recoveryKey.text.trim(); - final String recoveryKey = - CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); - final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey); - if (inputKey == recoveryKey || inputKey == recoveryKeyWords) { - try { - await UserRemoteFlagService.instance.markRecoveryVerificationAsDone(); - } catch (e) { - await dialog.hide(); - if (e is DioException && e.type == DioExceptionType.unknown) { - await showErrorDialog( - context, - "No internet connection", - "Please check your internet connection and try again.", - ); - } else { - await showGenericErrorDialog( - context: context, - error: e, - ); - } - return; - } - - await dialog.hide(); - // todo: change this as per figma once the component is ready - await showErrorDialog( - context, - context.l10n.recoveryKeyVerified, - context.l10n.recoveryKeySuccessBody, - ); - Navigator.of(context).pop(); - } else { - throw Exception("recovery key didn't match"); - } - } catch (e, s) { - _logger.severe("failed to verify recovery key", e, s); - await dialog.hide(); - final String errMessage = context.l10n.invalidRecoveryKey; - final result = await showChoiceDialog( - context, - title: context.l10n.invalidKey, - body: errMessage, - firstButtonLabel: context.l10n.tryAgain, - secondButtonLabel: context.l10n.viewRecoveryKey, - secondButtonAction: ButtonAction.second, - ); - if (result!.action == ButtonAction.second) { - await _onViewRecoveryKeyClick(); - } - } - } - - Future _onViewRecoveryKeyClick() async { - final hasAuthenticated = - await LocalAuthenticationService.instance.requestLocalAuthentication( - context, - "Please authenticate to view your recovery key", - ); - await PlatformUtil.refocusWindows(); - - if (hasAuthenticated) { - String recoveryKey; - try { - recoveryKey = - CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); - await routeToPage( - context, - RecoveryKeyPage( - recoveryKey, - context.l10n.ok, - showAppBar: true, - onDone: () { - Navigator.of(context).pop(); - }, - ), - ); - } catch (e) { - // ignore: unawaited_futures - showGenericErrorDialog( - context: context, - error: e, - ); - return; - } - } - } - - @override - Widget build(BuildContext context) { - final enteTheme = Theme.of(context).colorScheme.enteTheme; - return Scaffold( - appBar: AppBar( - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - color: Theme.of(context).iconTheme.color, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: constraints.maxWidth, - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - children: [ - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: Text( - context.l10n.confirmRecoveryKey, - style: enteTheme.textTheme.h3Bold, - textAlign: TextAlign.left, - ), - ), - const SizedBox(height: 18), - Text( - context.l10n.recoveryKeyVerifyReason, - style: enteTheme.textTheme.small - .copyWith(color: enteTheme.colorScheme.textMuted), - ), - const SizedBox(height: 12), - TextFormField( - decoration: InputDecoration( - filled: true, - hintText: context.l10n.enterYourRecoveryKey, - contentPadding: const EdgeInsets.all(20), - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - ), - style: const TextStyle( - fontSize: 14, - fontFeatures: [FontFeature.tabularFigures()], - ), - controller: _recoveryKey, - autofocus: false, - autocorrect: false, - keyboardType: TextInputType.multiline, - minLines: 4, - maxLines: null, - onChanged: (_) { - setState(() {}); - }, - ), - const SizedBox(height: 12), - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - width: double.infinity, - padding: const EdgeInsets.fromLTRB(0, 12, 0, 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - GradientButton( - onTap: _verifyRecoveryKey, - text: context.l10n.confirm, - ), - const SizedBox(height: 8), - ], - ), - ), - ), - const SizedBox(height: 20), - ], - ), - ), - ), - ); - }, - ), - ), - ); - } -}