From 6f94d91afb62bc25c21b819e288edb101f88c817 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Fri, 1 Aug 2025 11:48:04 +0530 Subject: [PATCH] More code refractor in auth/accounts section --- .../lib/onboarding/view/onboarding_page.dart | 18 +- .../apps/auth/lib/services/user_service.dart | 24 +- .../auth/lib/ui/account/email_entry_page.dart | 516 ------------------ .../apps/auth/lib/ui/account/login_page.dart | 227 -------- .../account/login_pwd_verification_page.dart | 345 ------------ .../lib/ui/account/ott_verification_page.dart | 223 -------- .../lib/ui/account/password_entry_page.dart | 515 ----------------- .../auth/lib/ui/account/recovery_page.dart | 9 +- mobile/apps/auth/lib/ui/passkey_page.dart | 235 -------- .../ui/settings/account_section_widget.dart | 9 +- 10 files changed, 37 insertions(+), 2084 deletions(-) delete mode 100644 mobile/apps/auth/lib/ui/account/email_entry_page.dart delete mode 100644 mobile/apps/auth/lib/ui/account/login_page.dart delete mode 100644 mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart delete mode 100644 mobile/apps/auth/lib/ui/account/ott_verification_page.dart delete mode 100644 mobile/apps/auth/lib/ui/account/password_entry_page.dart delete mode 100644 mobile/apps/auth/lib/ui/passkey_page.dart diff --git a/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart b/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart index 9037cf2652..c9fcd645f5 100644 --- a/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart +++ b/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:io'; import 'package:ente_accounts/pages/email_entry_page.dart'; +import 'package:ente_accounts/pages/login_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; import 'package:ente_auth/app/view/app.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/ente_theme_data.dart'; @@ -9,9 +11,7 @@ import 'package:ente_auth/events/trigger_logout_event.dart'; import "package:ente_auth/l10n/l10n.dart"; import 'package:ente_auth/locale.dart'; import 'package:ente_auth/theme/text_style.dart'; -import 'package:ente_auth/ui/account/login_page.dart'; import 'package:ente_auth/ui/account/logout_dialog.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/account/password_reentry_page.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; @@ -265,8 +265,10 @@ class _OnboardingPageState extends State { // No key if (Configuration.instance.getKeyAttributes() == null) { // Never had a key - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), ); } else if (Configuration.instance.getKey() == null) { // Yet to decrypt the key @@ -288,13 +290,15 @@ class _OnboardingPageState extends State { void _navigateToSignInPage() { Widget page; if (Configuration.instance.getEncryptedToken() == null) { - page = const LoginPage(); + page = LoginPage(Configuration.instance); } else { // No key if (Configuration.instance.getKeyAttributes() == null) { // Never had a key - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), ); } else if (Configuration.instance.getKey() == null) { // Yet to decrypt the key diff --git a/mobile/apps/auth/lib/services/user_service.dart b/mobile/apps/auth/lib/services/user_service.dart index c79016b3d4..cb676ad237 100644 --- a/mobile/apps/auth/lib/services/user_service.dart +++ b/mobile/apps/auth/lib/services/user_service.dart @@ -10,21 +10,21 @@ import 'package:ente_accounts/models/set_keys_request.dart'; import 'package:ente_accounts/models/set_recovery_key_request.dart'; import 'package:ente_accounts/models/two_factor.dart'; import 'package:ente_accounts/models/user_details.dart'; +import 'package:ente_accounts/pages/login_page.dart'; +import 'package:ente_accounts/pages/ott_verification_page.dart'; +import 'package:ente_accounts/pages/passkey_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; import 'package:ente_accounts/pages/two_factor_authentication_page.dart'; import 'package:ente_accounts/pages/two_factor_recovery_page.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/constants.dart'; import 'package:ente_auth/core/errors.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/api/user/srp.dart'; -import 'package:ente_auth/ui/account/login_page.dart'; -import 'package:ente_auth/ui/account/ott_verification_page.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; +import 'package:ente_auth/models/api/user/srp.dart'; import 'package:ente_auth/ui/account/password_reentry_page.dart'; import 'package:ente_auth/ui/account/recovery_page.dart'; import 'package:ente_auth/ui/common/progress_dialog.dart'; import 'package:ente_auth/ui/home_page.dart'; -import 'package:ente_auth/ui/passkey_page.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_base/models/key_attributes.dart'; @@ -405,6 +405,7 @@ class UserService { } if (passkeySessionID.isNotEmpty) { page = PasskeyPage( + Configuration.instance, passkeySessionID, totp2FASessionID: twoFASessionID, accountsUrl: accountsUrl, @@ -420,8 +421,10 @@ class UserService { page = const PasswordReentryPage(); } } else { - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), ); } } @@ -721,6 +724,7 @@ class UserService { Configuration.instance.setVolatilePassword(userPassword); if (passkeySessionID.isNotEmpty) { page = PasskeyPage( + Configuration.instance, passkeySessionID, totp2FASessionID: twoFASessionID, accountsUrl: accountsUrl, @@ -838,7 +842,7 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(Configuration.instance); }, ), (route) => route.isFirst, @@ -904,7 +908,7 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(Configuration.instance); }, ), (route) => route.isFirst, @@ -1002,7 +1006,7 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(Configuration.instance); }, ), (route) => route.isFirst, diff --git a/mobile/apps/auth/lib/ui/account/email_entry_page.dart b/mobile/apps/auth/lib/ui/account/email_entry_page.dart deleted file mode 100644 index 24cc77cbce..0000000000 --- a/mobile/apps/auth/lib/ui/account/email_entry_page.dart +++ /dev/null @@ -1,516 +0,0 @@ -// import 'package:email_validator/email_validator.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/user_service.dart'; -// import 'package:ente_auth/theme/ente_theme.dart'; -// import 'package:ente_auth/ui/common/dynamic_fab.dart'; -// import 'package:ente_auth/utils/platform_util.dart'; -// import 'package:ente_auth/utils/toast_util.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter/services.dart'; -// import 'package:password_strength/password_strength.dart'; -// import 'package:step_progress_indicator/step_progress_indicator.dart'; -// import "package:styled_text/styled_text.dart"; - -// class EmailEntryPage extends StatefulWidget { -// const EmailEntryPage({super.key}); - -// @override -// State createState() => _EmailEntryPageState(); -// } - -// class _EmailEntryPageState extends State { -// static const kMildPasswordStrengthThreshold = 0.4; -// static const kStrongPasswordStrengthThreshold = 0.7; - -// final _config = Configuration.instance; -// final _passwordController1 = TextEditingController(); -// final _passwordController2 = TextEditingController(); -// final Color _validFieldValueColor = const Color.fromARGB(51, 157, 45, 194); - -// String? _email; -// String? _password; -// String _cnfPassword = ''; -// String _referralSource = ''; -// double _passwordStrength = 0.0; -// bool _emailIsValid = false; -// bool _hasAgreedToTOS = true; -// bool _hasAgreedToE2E = false; -// bool _password1Visible = false; -// bool _password2Visible = false; -// bool _passwordsMatch = false; - -// final _password1FocusNode = FocusNode(); -// final _password2FocusNode = FocusNode(); -// bool _password1InFocus = false; -// bool _password2InFocus = false; -// bool _passwordIsValid = false; - -// @override -// void initState() { -// _email = _config.getEmail(); -// _password1FocusNode.addListener(() { -// setState(() { -// _password1InFocus = _password1FocusNode.hasFocus; -// }); -// }); -// _password2FocusNode.addListener(() { -// setState(() { -// _password2InFocus = _password2FocusNode.hasFocus; -// }); -// }); -// super.initState(); -// } - -// @override -// Widget build(BuildContext context) { -// final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; - -// FloatingActionButtonLocation? fabLocation() { -// if (isKeypadOpen) { -// return null; -// } else { -// return FloatingActionButtonLocation.centerFloat; -// } -// } - -// final appBar = AppBar( -// elevation: 0, -// leading: IconButton( -// icon: const Icon(Icons.arrow_back), -// color: Theme.of(context).iconTheme.color, -// onPressed: () { -// Navigator.of(context).pop(); -// }, -// ), -// title: Material( -// type: MaterialType.transparency, -// child: StepProgressIndicator( -// totalSteps: 4, -// currentStep: 1, -// selectedColor: Theme.of(context).colorScheme.alternativeColor, -// roundedEdges: const Radius.circular(10), -// unselectedColor: -// Theme.of(context).colorScheme.stepProgressUnselectedColor, -// ), -// ), -// ); -// return Scaffold( -// resizeToAvoidBottomInset: isKeypadOpen, -// appBar: appBar, -// body: _getBody(), -// floatingActionButton: DynamicFAB( -// isKeypadOpen: isKeypadOpen, -// isFormValid: _isFormValid(), -// buttonText: context.l10n.createAccount, -// onPressedFunction: () { -// UserService.instance.setEmail(_email!); -// _config.setVolatilePassword(_passwordController1.text); -// UserService.instance.setRefSource(_referralSource); -// UserService.instance.sendOtt( -// context, -// _email!, -// isCreateAccountScreen: true, -// purpose: "signup", -// ); -// FocusScope.of(context).unfocus(); -// }, -// ), -// floatingActionButtonLocation: fabLocation(), -// floatingActionButtonAnimator: NoScalingAnimation(), -// ); -// } - -// Widget _getBody() { -// var passwordStrengthText = context.l10n.weakStrength; -// var passwordStrengthColor = Colors.redAccent; -// if (_passwordStrength > kStrongPasswordStrengthThreshold) { -// passwordStrengthText = context.l10n.strongStrength; -// passwordStrengthColor = Colors.greenAccent; -// } else if (_passwordStrength > kMildPasswordStrengthThreshold) { -// passwordStrengthText = context.l10n.moderateStrength; -// passwordStrengthColor = Colors.orangeAccent; -// } -// return Column( -// children: [ -// Expanded( -// child: AutofillGroup( -// child: ListView( -// children: [ -// Padding( -// padding: -// const EdgeInsets.symmetric(vertical: 30, horizontal: 20), -// child: Text( -// context.l10n.createNewAccount, -// style: Theme.of(context).textTheme.headlineMedium, -// ), -// ), -// Padding( -// padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), -// child: TextFormField( -// style: Theme.of(context).textTheme.titleMedium, -// autofillHints: const [AutofillHints.email], -// decoration: InputDecoration( -// fillColor: _emailIsValid ? _validFieldValueColor : null, -// filled: true, -// hintText: context.l10n.email, -// contentPadding: const EdgeInsets.symmetric( -// horizontal: 16, -// vertical: 14, -// ), -// border: UnderlineInputBorder( -// borderSide: BorderSide.none, -// borderRadius: BorderRadius.circular(6), -// ), -// suffixIcon: _emailIsValid -// ? Icon( -// Icons.check, -// size: 20, -// color: Theme.of(context) -// .inputDecorationTheme -// .focusedBorder! -// .borderSide -// .color, -// ) -// : null, -// ), -// onChanged: (value) { -// _email = value.trim(); -// if (_emailIsValid != EmailValidator.validate(_email!)) { -// setState(() { -// _emailIsValid = EmailValidator.validate(_email!); -// }); -// } -// }, -// autocorrect: false, -// keyboardType: TextInputType.emailAddress, -// //initialValue: _email, -// textInputAction: TextInputAction.next, -// ), -// ), -// const Padding(padding: EdgeInsets.all(4)), -// Padding( -// padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), -// child: TextFormField( -// keyboardType: TextInputType.text, -// textInputAction: TextInputAction.next, -// controller: _passwordController1, -// obscureText: !_password1Visible, -// enableSuggestions: true, -// autofillHints: const [AutofillHints.newPassword], -// decoration: InputDecoration( -// fillColor: -// _passwordIsValid ? _validFieldValueColor : null, -// filled: true, -// hintText: context.l10n.password, -// contentPadding: const EdgeInsets.symmetric( -// horizontal: 16, -// vertical: 14, -// ), -// suffixIcon: _password1InFocus -// ? IconButton( -// icon: Icon( -// _password1Visible -// ? Icons.visibility -// : Icons.visibility_off, -// color: Theme.of(context).iconTheme.color, -// size: 20, -// ), -// onPressed: () { -// setState(() { -// _password1Visible = !_password1Visible; -// }); -// }, -// ) -// : _passwordIsValid -// ? Icon( -// Icons.check, -// color: Theme.of(context) -// .inputDecorationTheme -// .focusedBorder! -// .borderSide -// .color, -// ) -// : null, -// border: UnderlineInputBorder( -// borderSide: BorderSide.none, -// borderRadius: BorderRadius.circular(6), -// ), -// ), -// focusNode: _password1FocusNode, -// onChanged: (password) { -// if (password != _password) { -// setState(() { -// _password = password; -// _passwordStrength = -// estimatePasswordStrength(password); -// _passwordIsValid = _passwordStrength >= -// kMildPasswordStrengthThreshold; -// _passwordsMatch = _password == _cnfPassword; -// }); -// } -// }, -// onEditingComplete: () { -// _password1FocusNode.unfocus(); -// _password2FocusNode.requestFocus(); -// TextInput.finishAutofillContext(); -// }, -// ), -// ), -// const SizedBox(height: 8), -// Padding( -// padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), -// child: TextFormField( -// keyboardType: TextInputType.visiblePassword, -// controller: _passwordController2, -// obscureText: !_password2Visible, -// autofillHints: const [AutofillHints.newPassword], -// onEditingComplete: () => TextInput.finishAutofillContext(), -// decoration: InputDecoration( -// fillColor: _passwordsMatch && _passwordIsValid -// ? _validFieldValueColor -// : null, -// filled: true, -// hintText: context.l10n.confirmPassword, -// contentPadding: const EdgeInsets.symmetric( -// horizontal: 16, -// vertical: 14, -// ), -// suffixIcon: _password2InFocus -// ? IconButton( -// icon: Icon( -// _password2Visible -// ? Icons.visibility -// : Icons.visibility_off, -// color: Theme.of(context).iconTheme.color, -// size: 20, -// ), -// onPressed: () { -// setState(() { -// _password2Visible = !_password2Visible; -// }); -// }, -// ) -// : _passwordsMatch -// ? Icon( -// Icons.check, -// color: Theme.of(context) -// .inputDecorationTheme -// .focusedBorder! -// .borderSide -// .color, -// ) -// : null, -// border: UnderlineInputBorder( -// borderSide: BorderSide.none, -// borderRadius: BorderRadius.circular(6), -// ), -// ), -// focusNode: _password2FocusNode, -// onChanged: (cnfPassword) { -// setState(() { -// _cnfPassword = cnfPassword; -// if (_password != null || _password != '') { -// _passwordsMatch = _password == _cnfPassword; -// } -// }); -// }, -// ), -// ), -// Opacity( -// opacity: (_password != '') && _password1InFocus ? 1 : 0, -// child: Padding( -// padding: -// const EdgeInsets.symmetric(horizontal: 24, vertical: 8), -// child: Text( -// context.l10n.passwordStrength(passwordStrengthText), -// style: TextStyle( -// color: passwordStrengthColor, -// fontWeight: FontWeight.w500, -// fontSize: 12, -// ), -// ), -// ), -// ), -// const SizedBox(height: 4), -// Padding( -// padding: -// const EdgeInsets.symmetric(vertical: 0, horizontal: 20), -// child: Text( -// context.l10n.hearUsWhereTitle, -// style: getEnteTextTheme(context).smallFaint, -// ), -// ), -// const SizedBox(height: 4), -// Padding( -// padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), -// child: TextFormField( -// style: Theme.of(context).textTheme.titleMedium, -// decoration: InputDecoration( -// fillColor: null, -// filled: true, -// contentPadding: const EdgeInsets.symmetric( -// horizontal: 16, -// vertical: 14, -// ), -// border: UnderlineInputBorder( -// borderSide: BorderSide.none, -// borderRadius: BorderRadius.circular(6), -// ), -// suffixIcon: InkWell( -// onTap: () { -// showToast( -// context, -// context.l10n.hearUsExplanation, -// ); -// }, -// child: Icon( -// Icons.info_outline_rounded, -// color: getEnteColorScheme(context).strokeMuted, -// ), -// ), -// ), -// onChanged: (value) { -// _referralSource = value.trim(); -// }, -// autocorrect: false, -// keyboardType: TextInputType.text, -// textInputAction: TextInputAction.next, -// ), -// ), -// const Divider(thickness: 1), -// const SizedBox(height: 12), -// _getAgreement(), -// const SizedBox(height: 40), -// ], -// ), -// ), -// ), -// ], -// ); -// } - -// Container _getAgreement() { -// return Container( -// padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), -// child: Column( -// children: [ -// _getTOSAgreement(), -// _getPasswordAgreement(), -// ], -// ), -// ); -// } - -// Widget _getTOSAgreement() { -// return GestureDetector( -// onTap: () { -// setState(() { -// _hasAgreedToTOS = !_hasAgreedToTOS; -// }); -// }, -// behavior: HitTestBehavior.translucent, -// child: Row( -// children: [ -// Checkbox( -// value: _hasAgreedToTOS, -// side: CheckboxTheme.of(context).side, -// onChanged: (value) { -// setState(() { -// _hasAgreedToTOS = value!; -// }); -// }, -// ), -// Expanded( -// child: StyledText( -// text: context.l10n.signUpTerms, -// style: Theme.of(context) -// .textTheme -// .titleMedium! -// .copyWith(fontSize: 12), -// tags: { -// 'u-terms': StyledTextActionTag( -// (String? text, Map attrs) => -// PlatformUtil.openWebView( -// context, -// context.l10n.termsOfServicesTitle, -// "https://ente.io/terms", -// ), -// style: const TextStyle( -// decoration: TextDecoration.underline, -// ), -// ), -// 'u-policy': StyledTextActionTag( -// (String? text, Map attrs) => -// PlatformUtil.openWebView( -// context, -// context.l10n.privacyPolicyTitle, -// "https://ente.io/privacy", -// ), -// style: const TextStyle( -// decoration: TextDecoration.underline, -// ), -// ), -// }, -// ), -// ), -// ], -// ), -// ); -// } - -// Widget _getPasswordAgreement() { -// return GestureDetector( -// onTap: () { -// setState(() { -// _hasAgreedToE2E = !_hasAgreedToE2E; -// }); -// }, -// behavior: HitTestBehavior.translucent, -// child: Row( -// children: [ -// Checkbox( -// value: _hasAgreedToE2E, -// side: CheckboxTheme.of(context).side, -// onChanged: (value) { -// setState(() { -// _hasAgreedToE2E = value!; -// }); -// }, -// ), -// Expanded( -// child: StyledText( -// text: context.l10n.ackPasswordLostWarning, -// style: Theme.of(context) -// .textTheme -// .titleMedium! -// .copyWith(fontSize: 12), -// tags: { -// 'underline': StyledTextActionTag( -// (String? text, Map attrs) => -// PlatformUtil.openWebView( -// context, -// context.l10n.encryption, -// "https://ente.io/architecture", -// ), -// style: const TextStyle( -// decoration: TextDecoration.underline, -// ), -// ), -// }, -// ), -// ), -// ], -// ), -// ); -// } - -// bool _isFormValid() { -// return _emailIsValid && -// _passwordsMatch && -// _hasAgreedToTOS && -// _hasAgreedToE2E && -// _passwordIsValid; -// } -// } diff --git a/mobile/apps/auth/lib/ui/account/login_page.dart b/mobile/apps/auth/lib/ui/account/login_page.dart deleted file mode 100644 index fef05707ac..0000000000 --- a/mobile/apps/auth/lib/ui/account/login_page.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:email_validator/email_validator.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/errors.dart'; -import "package:ente_auth/l10n/l10n.dart"; -import 'package:ente_auth/models/api/user/srp.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/account/login_pwd_verification_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; -import "package:styled_text/styled_text.dart"; - -class LoginPage extends StatefulWidget { - const LoginPage({super.key}); - - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - final _config = Configuration.instance; - bool _emailIsValid = false; - String? _email; - Color? _emailInputFieldColor; - final Logger _logger = Logger('_LoginPageState'); - - Future onPressed() async { - await UserService.instance.setEmail(_email!); - Configuration.instance.resetVolatilePassword(); - SrpAttributes? attr; - bool isEmailVerificationEnabled = true; - try { - attr = await UserService.instance.getSrpAttributes(_email!); - isEmailVerificationEnabled = attr.isEmailMFAEnabled; - } catch (e) { - if (e is! SrpSetupNotCompleteError) { - _logger.severe('Error getting SRP attributes', e); - } - } - if (attr != null && !isEmailVerificationEnabled) { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return LoginPasswordVerificationPage( - srpAttributes: attr!, - ); - }, - ), - ); - } else { - await UserService.instance.sendOtt( - context, - _email!, - isCreateAccountScreen: false, - purpose: 'login', - ); - } - FocusScope.of(context).unfocus(); - } - - @override - void initState() { - _email = _config.getEmail(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; - - FloatingActionButtonLocation? fabLocation() { - if (isKeypadOpen) { - return null; - } else { - return FloatingActionButtonLocation.centerFloat; - } - } - - return Scaffold( - resizeToAvoidBottomInset: isKeypadOpen, - appBar: AppBar( - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - color: Theme.of(context).iconTheme.color, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: _getBody(), - floatingActionButton: DynamicFAB( - isKeypadOpen: isKeypadOpen, - isFormValid: _emailIsValid, - buttonText: context.l10n.logInLabel, - onPressedFunction: onPressed, - ), - floatingActionButtonLocation: fabLocation(), - floatingActionButtonAnimator: NoScalingAnimation(), - ); - } - - Widget _getBody() { - final l10n = context.l10n; - return Column( - children: [ - Expanded( - child: AutofillGroup( - child: ListView( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(vertical: 30, horizontal: 20), - child: Text( - l10n.welcomeBack, - style: Theme.of(context).textTheme.headlineMedium, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), - child: TextFormField( - autofillHints: const [AutofillHints.email], - onFieldSubmitted: - _emailIsValid ? (value) => onPressed() : null, - decoration: InputDecoration( - fillColor: _emailInputFieldColor, - filled: true, - hintText: l10n.email, - contentPadding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 15, - ), - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - suffixIcon: _emailIsValid - ? Icon( - Icons.check, - size: 20, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, - ) - : null, - ), - onChanged: (value) { - setState(() { - _email = value.trim(); - _emailIsValid = EmailValidator.validate(_email!); - if (_emailIsValid) { - _emailInputFieldColor = - const Color.fromARGB(51, 157, 45, 194); - } else { - _emailInputFieldColor = null; - } - }); - }, - autocorrect: false, - keyboardType: TextInputType.emailAddress, - //initialValue: _email, - autofocus: true, - ), - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 18), - child: Divider( - thickness: 1, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - children: [ - Expanded( - flex: 5, - child: StyledText( - text: context.l10n.loginTerms, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 12), - tags: { - 'u-terms': StyledTextActionTag( - (String? text, Map attrs) => - PlatformUtil.openWebView( - context, - context.l10n.termsOfServicesTitle, - "https://ente.io/terms", - ), - style: const TextStyle( - decoration: TextDecoration.underline, - ), - ), - 'u-policy': StyledTextActionTag( - (String? text, Map attrs) => - PlatformUtil.openWebView( - context, - context.l10n.privacyPolicyTitle, - "https://ente.io/privacy", - ), - style: const TextStyle( - decoration: TextDecoration.underline, - ), - ), - }, - ), - ), - Expanded( - flex: 2, - child: Container(), - ), - ], - ), - ), - ], - ), - ), - ), - const Padding(padding: EdgeInsets.all(8)), - ], - ); - } -} diff --git a/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart b/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart deleted file mode 100644 index 6d43988e2c..0000000000 --- a/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart +++ /dev/null @@ -1,345 +0,0 @@ -import "package:dio/dio.dart"; -import 'package:ente_auth/core/configuration.dart'; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/models/api/user/srp.dart"; -import "package:ente_auth/services/user_service.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import "package:ente_auth/ui/components/buttons/button_widget.dart"; -import "package:ente_auth/utils/dialog_util.dart"; -import "package:ente_auth/utils/email_util.dart"; -import "package:ente_crypto_dart/ente_crypto_dart.dart"; -import 'package:flutter/material.dart'; -import "package:logging/logging.dart"; - -// LoginPasswordVerificationPage is a page that allows the user to enter their password to verify their identity. -// If the password is correct, then the user is either directed to -// PasswordReentryPage (if the user has not yet set up 2FA) or TwoFactorAuthenticationPage (if the user has set up 2FA). -// In the PasswordReentryPage, the password is auto-filled based on the -// volatile password. -class LoginPasswordVerificationPage extends StatefulWidget { - final SrpAttributes srpAttributes; - const LoginPasswordVerificationPage({super.key, required this.srpAttributes}); - - @override - State createState() => - _LoginPasswordVerificationPageState(); -} - -class _LoginPasswordVerificationPageState - extends State { - final _logger = Logger((_LoginPasswordVerificationPageState).toString()); - final _passwordController = TextEditingController(); - final FocusNode _passwordFocusNode = FocusNode(); - String? email; - bool _passwordInFocus = false; - bool _passwordVisible = false; - - Future onPressed() async { - FocusScope.of(context).unfocus(); - await verifyPassword(context, _passwordController.text); - } - - @override - void initState() { - super.initState(); - email = Configuration.instance.getEmail(); - _passwordFocusNode.addListener(() { - setState(() { - _passwordInFocus = _passwordFocusNode.hasFocus; - }); - }); - } - - @override - Widget build(BuildContext context) { - final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; - - FloatingActionButtonLocation? fabLocation() { - if (isKeypadOpen) { - return null; - } else { - return FloatingActionButtonLocation.centerFloat; - } - } - - return Scaffold( - resizeToAvoidBottomInset: isKeypadOpen, - appBar: AppBar( - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - color: Theme.of(context).iconTheme.color, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - body: _getBody(), - floatingActionButton: DynamicFAB( - key: const ValueKey("verifyPasswordButton"), - isKeypadOpen: isKeypadOpen, - isFormValid: _passwordController.text.isNotEmpty, - buttonText: context.l10n.logInLabel, - onPressedFunction: onPressed, - ), - floatingActionButtonLocation: fabLocation(), - floatingActionButtonAnimator: NoScalingAnimation(), - ); - } - - Future verifyPassword(BuildContext context, String password) async { - final dialog = createProgressDialog( - context, - context.l10n.pleaseWait, - isDismissible: true, - ); - await dialog.show(); - try { - await UserService.instance.verifyEmailViaPassword( - context, - widget.srpAttributes, - password, - dialog, - ); - } on DioException catch (e, s) { - await dialog.hide(); - if (e.response != null && e.response!.statusCode == 401) { - _logger.severe('server reject, failed verify SRP login', e, s); - await _showContactSupportDialog( - context, - context.l10n.incorrectPasswordTitle, - context.l10n.pleaseTryAgain, - ); - } else { - _logger.severe('API failure during SRP login', e, s); - if (e.type == DioExceptionType.connectionError) { - await _showContactSupportDialog( - context, - context.l10n.noInternetConnection, - context.l10n.pleaseCheckYourInternetConnectionAndTryAgain, - ); - } else { - await _showContactSupportDialog( - context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, - ); - } - } - } catch (e, s) { - _logger.info('error during loginViaPassword', e); - await dialog.hide(); - if (e is LoginKeyDerivationError) { - _logger.severe('loginKey derivation error', e, s); - // LoginKey err, perform regular login via ott verification - await UserService.instance.sendOtt( - context, - email!, - isCreateAccountScreen: true, - ); - return; - } else if (e is KeyDerivationError) { - // device is not powerful enough to perform derive key - final dialogChoice = await showChoiceDialog( - context, - title: context.l10n.recreatePasswordTitle, - body: context.l10n.recreatePasswordBody, - firstButtonLabel: context.l10n.useRecoveryKey, - ); - if (dialogChoice!.action == ButtonAction.first) { - await UserService.instance.sendOtt( - context, - email!, - isResetPasswordScreen: true, - ); - } - return; - } else { - _logger.severe('unexpected error while verifying password', e, s); - await _showContactSupportDialog( - context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, - ); - } - } - } - - Future _showContactSupportDialog( - BuildContext context, - String title, - String message, - ) async { - final dialogChoice = await showChoiceDialog( - context, - title: title, - body: message, - firstButtonLabel: context.l10n.contactSupport, - secondButtonLabel: context.l10n.ok, - ); - if (dialogChoice!.action == ButtonAction.first) { - await sendLogs( - context, - context.l10n.contactSupport, - postShare: () {}, - ); - } - } - - Widget _getBody() { - return Column( - children: [ - Expanded( - child: AutofillGroup( - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.only(top: 30, left: 20, right: 20), - child: Text( - context.l10n.enterPassword, - style: Theme.of(context).textTheme.headlineMedium, - ), - ), - Padding( - padding: const EdgeInsets.only( - bottom: 30, - left: 22, - right: 20, - ), - child: Text( - email ?? '', - style: getEnteTextTheme(context).smallMuted, - ), - ), - Visibility( - // hidden textForm for suggesting auto-fill service for saving - // password - visible: false, - child: TextFormField( - autofillHints: const [ - AutofillHints.email, - ], - autocorrect: false, - keyboardType: TextInputType.emailAddress, - initialValue: email, - textInputAction: TextInputAction.next, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), - child: TextFormField( - onFieldSubmitted: _passwordController.text.isNotEmpty - ? (_) => onPressed() - : null, - key: const ValueKey("passwordInputField"), - autofillHints: const [AutofillHints.password], - decoration: InputDecoration( - hintText: context.l10n.enterYourPasswordHint, - filled: true, - contentPadding: const EdgeInsets.all(20), - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - suffixIcon: _passwordInFocus - ? IconButton( - icon: Icon( - _passwordVisible - ? Icons.visibility - : Icons.visibility_off, - color: Theme.of(context).iconTheme.color, - size: 20, - ), - onPressed: () { - setState(() { - _passwordVisible = !_passwordVisible; - }); - }, - ) - : null, - ), - style: const TextStyle( - fontSize: 14, - ), - controller: _passwordController, - autofocus: true, - autocorrect: false, - obscureText: !_passwordVisible, - keyboardType: TextInputType.visiblePassword, - focusNode: _passwordFocusNode, - onChanged: (_) { - setState(() {}); - }, - ), - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 18), - child: Divider( - thickness: 1, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () async { - await UserService.instance.sendOtt( - context, - email!, - isResetPasswordScreen: true, - ); - }, - child: Center( - child: Text( - context.l10n.forgotPassword, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), - ), - ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () async { - final dialog = createProgressDialog( - context, - context.l10n.pleaseWait, - ); - await dialog.show(); - await Configuration.instance.logout(); - await dialog.hide(); - Navigator.of(context) - .popUntil((route) => route.isFirst); - }, - child: Center( - child: Text( - context.l10n.changeEmail, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ], - ); - } -} diff --git a/mobile/apps/auth/lib/ui/account/ott_verification_page.dart b/mobile/apps/auth/lib/ui/account/ott_verification_page.dart deleted file mode 100644 index cc9661defc..0000000000 --- a/mobile/apps/auth/lib/ui/account/ott_verification_page.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:flutter/material.dart'; -import 'package:step_progress_indicator/step_progress_indicator.dart'; -import 'package:styled_text/styled_text.dart'; - -class OTTVerificationPage extends StatefulWidget { - final String email; - final bool isChangeEmail; - final bool isCreateAccountScreen; - final bool isResetPasswordScreen; - - const OTTVerificationPage( - this.email, { - this.isChangeEmail = false, - this.isCreateAccountScreen = false, - this.isResetPasswordScreen = false, - super.key, - }); - - @override - State createState() => _OTTVerificationPageState(); -} - -class _OTTVerificationPageState extends State { - final _verificationCodeController = TextEditingController(); - - Future onPressed() async { - if (widget.isChangeEmail) { - await UserService.instance.changeEmail( - context, - widget.email, - _verificationCodeController.text, - ); - } else { - await UserService.instance.verifyEmail( - context, - _verificationCodeController.text, - isResettingPasswordScreen: widget.isResetPasswordScreen, - ); - } - FocusScope.of(context).unfocus(); - } - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; - - FloatingActionButtonLocation? fabLocation() { - if (isKeypadOpen) { - return null; - } else { - return FloatingActionButtonLocation.centerFloat; - } - } - - return Scaffold( - resizeToAvoidBottomInset: isKeypadOpen, - appBar: AppBar( - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - color: Theme.of(context).iconTheme.color, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: widget.isCreateAccountScreen - ? Material( - type: MaterialType.transparency, - child: StepProgressIndicator( - totalSteps: 4, - currentStep: 2, - selectedColor: Theme.of(context).colorScheme.alternativeColor, - roundedEdges: const Radius.circular(10), - unselectedColor: - Theme.of(context).colorScheme.stepProgressUnselectedColor, - ), - ) - : null, - ), - body: _getBody(), - floatingActionButton: DynamicFAB( - isKeypadOpen: isKeypadOpen, - isFormValid: _verificationCodeController.text.isNotEmpty, - buttonText: l10n.verify, - onPressedFunction: onPressed, - ), - floatingActionButtonLocation: fabLocation(), - floatingActionButtonAnimator: NoScalingAnimation(), - ); - } - - Widget _getBody() { - final l10n = context.l10n; - return ListView( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(20, 30, 20, 15), - child: Text( - l10n.verifyEmail, - style: Theme.of(context).textTheme.headlineMedium, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 12), - child: StyledText( - text: l10n.weHaveSendEmailTo(widget.email), - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 14), - tags: { - 'green': StyledTextTag( - style: TextStyle( - color: Theme.of(context) - .colorScheme - .alternativeColor, - ), - ), - }, - ), - ), - widget.isResetPasswordScreen - ? Text( - l10n.toResetVerifyEmail, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 14), - ) - : Text( - l10n.checkInboxAndSpamFolder, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 14), - ), - ], - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width * 0.2, - height: 1, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), - child: TextFormField( - style: Theme.of(context).textTheme.titleMedium, - onFieldSubmitted: _verificationCodeController.text.isNotEmpty - ? (_) => onPressed() - : null, - decoration: InputDecoration( - filled: true, - hintText: l10n.tapToEnterCode, - contentPadding: const EdgeInsets.all(15), - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - ), - controller: _verificationCodeController, - autofocus: true, - autocorrect: false, - keyboardType: TextInputType.number, - onChanged: (_) { - setState(() {}); - }, - ), - ), - const Divider( - thickness: 1, - ), - Padding( - padding: const EdgeInsets.all(20), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - UserService.instance.sendOtt( - context, - widget.email, - isCreateAccountScreen: widget.isCreateAccountScreen, - isChangeEmail: widget.isChangeEmail, - isResetPasswordScreen: widget.isResetPasswordScreen, - ); - }, - child: Text( - l10n.resendEmail, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), - ), - ], - ), - ), - ], - ), - ], - ); - // ); - } -} diff --git a/mobile/apps/auth/lib/ui/account/password_entry_page.dart b/mobile/apps/auth/lib/ui/account/password_entry_page.dart deleted file mode 100644 index acaf051197..0000000000 --- a/mobile/apps/auth/lib/ui/account/password_entry_page.dart +++ /dev/null @@ -1,515 +0,0 @@ -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/account/recovery_key_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/components/models/button_type.dart'; -import 'package:ente_auth/ui/home_page.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_auth/utils/toast_util.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:logging/logging.dart'; -import 'package:password_strength/password_strength.dart'; -import 'package:styled_text/styled_text.dart'; - -enum PasswordEntryMode { - set, - update, - reset, -} - -class PasswordEntryPage extends StatefulWidget { - final PasswordEntryMode mode; - - const PasswordEntryPage({required this.mode, super.key}); - - @override - State createState() => _PasswordEntryPageState(); -} - -class _PasswordEntryPageState extends State { - static const kMildPasswordStrengthThreshold = 0.4; - static const kStrongPasswordStrengthThreshold = 0.7; - - final _logger = Logger((_PasswordEntryPageState).toString()); - final _passwordController1 = TextEditingController(), - _passwordController2 = TextEditingController(); - final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2); - String? _volatilePassword; - String _passwordInInputBox = ''; - String _passwordInInputConfirmationBox = ''; - double _passwordStrength = 0.0; - bool _password1Visible = false; - bool _password2Visible = false; - final _password1FocusNode = FocusNode(); - final _password2FocusNode = FocusNode(); - bool _password1InFocus = false; - bool _password2InFocus = false; - - bool _passwordsMatch = false; - bool _isPasswordValid = false; - - @override - void initState() { - super.initState(); - _volatilePassword = Configuration.instance.getVolatilePassword(); - if (_volatilePassword != null) { - Future.delayed( - Duration.zero, - () => _showRecoveryCodeDialog( - _volatilePassword!, - usingVolatilePassword: true, - ), - ); - } - _password1FocusNode.addListener(() { - setState(() { - _password1InFocus = _password1FocusNode.hasFocus; - }); - }); - _password2FocusNode.addListener(() { - setState(() { - _password2InFocus = _password2FocusNode.hasFocus; - }); - }); - } - - @override - Widget build(BuildContext context) { - final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; - - FloatingActionButtonLocation? fabLocation() { - if (isKeypadOpen) { - return null; - } else { - return FloatingActionButtonLocation.centerFloat; - } - } - - String title = context.l10n.setPasswordTitle; - if (widget.mode == PasswordEntryMode.update) { - title = context.l10n.changePasswordTitle; - } else if (widget.mode == PasswordEntryMode.reset) { - title = context.l10n.resetPasswordTitle; - } else if (_volatilePassword != null) { - title = context.l10n.encryptionKeys; - } - return Scaffold( - resizeToAvoidBottomInset: isKeypadOpen, - appBar: AppBar( - leading: widget.mode == PasswordEntryMode.reset - ? Container() - : IconButton( - icon: const Icon(Icons.arrow_back), - color: Theme.of(context).iconTheme.color, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - elevation: 0, - ), - body: _getBody(title), - floatingActionButton: DynamicFAB( - isKeypadOpen: isKeypadOpen, - isFormValid: _passwordsMatch && _isPasswordValid, - buttonText: title, - onPressedFunction: () { - if (widget.mode == PasswordEntryMode.set) { - _showRecoveryCodeDialog(_passwordController1.text); - } else { - _updatePassword(); - } - FocusScope.of(context).unfocus(); - }, - ), - floatingActionButtonLocation: fabLocation(), - floatingActionButtonAnimator: NoScalingAnimation(), - ); - } - - Widget _getBody(String buttonTextAndHeading) { - final email = Configuration.instance.getEmail(); - var passwordStrengthText = context.l10n.weakStrength; - var passwordStrengthColor = Colors.redAccent; - if (_passwordStrength > kStrongPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.strongStrength; - passwordStrengthColor = Colors.greenAccent; - } else if (_passwordStrength > kMildPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.moderateStrength; - passwordStrengthColor = Colors.orangeAccent; - } - if (_volatilePassword != null) { - return Container(); - } - return Column( - children: [ - Expanded( - child: AutofillGroup( - child: FocusTraversalGroup( - policy: OrderedTraversalPolicy(), - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 30, - horizontal: 20, - ), - child: Text( - buttonTextAndHeading, - style: Theme.of(context).textTheme.headlineMedium, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - widget.mode == PasswordEntryMode.set - ? context.l10n.enterPasswordToEncrypt - : context.l10n.enterNewPasswordToEncrypt, - textAlign: TextAlign.start, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 14), - ), - ), - const Padding(padding: EdgeInsets.all(8)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: StyledText( - text: context.l10n.passwordWarning, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 14), - tags: { - 'underline': StyledTextTag( - style: - Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), - }, - ), - ), - const Padding(padding: EdgeInsets.all(12)), - Visibility( - // hidden textForm for suggesting auto-fill service for saving - // password - visible: false, - child: TextFormField( - autofillHints: const [ - AutofillHints.email, - ], - autocorrect: false, - keyboardType: TextInputType.emailAddress, - initialValue: email, - textInputAction: TextInputAction.next, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), - child: TextFormField( - autofillHints: const [AutofillHints.newPassword], - onFieldSubmitted: (_) { - do { - FocusScope.of(context).nextFocus(); - } while (FocusScope.of(context).focusedChild!.context == - null); - }, - decoration: InputDecoration( - fillColor: - _isPasswordValid ? _validFieldValueColor : null, - filled: true, - hintText: context.l10n.password, - contentPadding: const EdgeInsets.all(20), - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - suffixIcon: _password1InFocus - ? IconButton( - icon: Icon( - _password1Visible - ? Icons.visibility - : Icons.visibility_off, - color: Theme.of(context).iconTheme.color, - size: 20, - ), - onPressed: () { - setState(() { - _password1Visible = !_password1Visible; - }); - }, - ) - : _isPasswordValid - ? Icon( - Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, - ) - : null, - ), - obscureText: !_password1Visible, - controller: _passwordController1, - autofocus: false, - autocorrect: false, - keyboardType: TextInputType.visiblePassword, - onChanged: (password) { - setState(() { - _passwordInInputBox = password; - _passwordStrength = - estimatePasswordStrength(password); - _isPasswordValid = _passwordStrength >= - kMildPasswordStrengthThreshold; - _passwordsMatch = _passwordInInputBox == - _passwordInInputConfirmationBox; - }); - }, - textInputAction: TextInputAction.next, - focusNode: _password1FocusNode, - ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), - child: TextFormField( - keyboardType: TextInputType.visiblePassword, - controller: _passwordController2, - obscureText: !_password2Visible, - autofillHints: const [AutofillHints.newPassword], - onEditingComplete: () => - TextInput.finishAutofillContext(), - decoration: InputDecoration( - fillColor: - _passwordsMatch ? _validFieldValueColor : null, - filled: true, - hintText: context.l10n.confirmPassword, - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 20, - ), - suffixIcon: _password2InFocus - ? IconButton( - icon: Icon( - _password2Visible - ? Icons.visibility - : Icons.visibility_off, - color: Theme.of(context).iconTheme.color, - size: 20, - ), - onPressed: () { - setState(() { - _password2Visible = !_password2Visible; - }); - }, - ) - : _passwordsMatch - ? Icon( - Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, - ) - : null, - border: UnderlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(6), - ), - ), - focusNode: _password2FocusNode, - onChanged: (cnfPassword) { - setState(() { - _passwordInInputConfirmationBox = cnfPassword; - if (_passwordInInputBox != '') { - _passwordsMatch = _passwordInInputBox == - _passwordInInputConfirmationBox; - } - }); - }, - ), - ), - Opacity( - opacity: (_passwordInInputBox != '') && _password1InFocus - ? 1 - : 0, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8, - ), - child: Text( - context.l10n.passwordStrength(passwordStrengthText), - style: TextStyle( - color: passwordStrengthColor, - ), - ), - ), - ), - const SizedBox(height: 8), - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - PlatformUtil.openWebView( - context, - context.l10n.howItWorks, - "https://ente.io/architecture", - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: RichText( - text: TextSpan( - text: context.l10n.howItWorks, - style: - Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), - ), - ), - ), - const Padding(padding: EdgeInsets.all(20)), - ], - ), - ), - ), - ), - ], - ); - } - - void _updatePassword() async { - final logOutFromOthers = await logOutFromOtherDevices(context); - final dialog = - createProgressDialog(context, context.l10n.generatingEncryptionKeys); - await dialog.show(); - try { - final result = await Configuration.instance - .getAttributesForNewPassword(_passwordController1.text); - await UserService.instance.updateKeyAttributes( - result.item1, - result.item2, - logoutOtherDevices: logOutFromOthers, - ); - await dialog.hide(); - showShortToast(context, context.l10n.passwordChangedSuccessfully); - Navigator.of(context).pop(); - if (widget.mode == PasswordEntryMode.reset) { - Navigator.of(context).popUntil((route) => route.isFirst); - } - } catch (e, s) { - _logger.severe(e, s); - await dialog.hide(); - // ignore: unawaited_futures - showGenericErrorDialog( - context: context, - error: e, - ); - } - } - - Future logOutFromOtherDevices(BuildContext context) async { - bool logOutFromOther = true; - await showChoiceDialog( - context, - title: context.l10n.signOutFromOtherDevices, - body: context.l10n.signOutOtherBody, - isDismissible: false, - firstButtonLabel: context.l10n.signOutOtherDevices, - firstButtonType: ButtonType.critical, - firstButtonOnTap: () async { - logOutFromOther = true; - }, - secondButtonLabel: context.l10n.doNotSignOut, - secondButtonOnTap: () async { - logOutFromOther = false; - }, - ); - return logOutFromOther; - } - - Future _showRecoveryCodeDialog( - String password, { - bool usingVolatilePassword = false, - }) async { - final l10n = context.l10n; - final dialog = - createProgressDialog(context, l10n.generatingEncryptionKeysTitle); - await dialog.show(); - try { - if (usingVolatilePassword) { - _logger.info('Using volatile password'); - } - final result = - await Configuration.instance.generateKey(password); - Configuration.instance.resetVolatilePassword(); - await dialog.hide(); - onDone() async { - final dialog = createProgressDialog(context, l10n.pleaseWait); - await dialog.show(); - try { - await UserService.instance.setAttributes(result); - await dialog.hide(); - Configuration.instance.resetVolatilePassword(); - // ignore: unawaited_futures - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) { - return const HomePage(); - }, - ), - (route) => route.isFirst, - ); - } catch (e, s) { - _logger.severe(e, s); - await dialog.hide(); - // ignore: unawaited_futures - showGenericErrorDialog( - context: context, - error: e, - ); - } - } - - // ignore: unawaited_futures - routeToPage( - context, - RecoveryKeyPage( - result.privateKeyAttributes.recoveryKey, - context.l10n.continueLabel, - showAppBar: false, - isDismissible: false, - onDone: onDone, - showProgressBar: true, - ), - ); - } catch (e) { - _logger.severe(e); - await dialog.hide(); - if (e is UnsupportedError) { - // ignore: unawaited_futures - showErrorDialog( - context, - context.l10n.insecureDevice, - context.l10n.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease, - ); - } else { - // ignore: unawaited_futures - showGenericErrorDialog( - context: context, - error: e, - ); - } - } - } -} diff --git a/mobile/apps/auth/lib/ui/account/recovery_page.dart b/mobile/apps/auth/lib/ui/account/recovery_page.dart index 137b8ce437..cb3619344d 100644 --- a/mobile/apps/auth/lib/ui/account/recovery_page.dart +++ b/mobile/apps/auth/lib/ui/account/recovery_page.dart @@ -1,7 +1,8 @@ +import 'package:ente_accounts/pages/password_entry_page.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; +import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; @@ -27,10 +28,12 @@ class _RecoveryPageState extends State { await Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (BuildContext context) { - return const PopScope( + return PopScope( canPop: false, child: PasswordEntryPage( - mode: PasswordEntryMode.reset, + Configuration.instance, + PasswordEntryMode.reset, + const HomePage(), ), ); }, diff --git a/mobile/apps/auth/lib/ui/passkey_page.dart b/mobile/apps/auth/lib/ui/passkey_page.dart deleted file mode 100644 index d4b37c615b..0000000000 --- a/mobile/apps/auth/lib/ui/passkey_page.dart +++ /dev/null @@ -1,235 +0,0 @@ -import 'dart:convert'; - -import 'package:app_links/app_links.dart'; -import 'package:ente_accounts/models/two_factor.dart'; -import 'package:ente_accounts/pages/two_factor_authentication_page.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/components/buttons/button_widget.dart'; -import 'package:ente_auth/ui/components/models/button_type.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/navigation_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; -import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -class PasskeyPage extends StatefulWidget { - final String sessionID; - final String totp2FASessionID; - final String accountsUrl; - - const PasskeyPage( - this.sessionID, { - required this.totp2FASessionID, - required this.accountsUrl, - super.key, - }); - - @override - State createState() => _PasskeyPageState(); -} - -class _PasskeyPageState extends State { - final Logger _logger = Logger("PasskeyPage"); - - @override - void initState() { - launchPasskey(); - _initDeepLinks(); - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - Future launchPasskey() async { - await launchUrlString( - "${widget.accountsUrl}/passkeys/verify?" - "passkeySessionID=${widget.sessionID}" - "&redirect=enteauth://passkey" - "&clientPackage=io.ente.auth", - mode: LaunchMode.externalApplication, - ); - } - - Future checkStatus() async { - late dynamic response; - try { - response = await UserService.instance - .getTokenForPasskeySession(widget.sessionID); - } on PassKeySessionNotVerifiedError { - showToast(context, context.l10n.passKeyPendingVerification); - return; - } on PassKeySessionExpiredError { - await showErrorDialog( - context, - context.l10n.loginSessionExpired, - context.l10n.loginSessionExpiredDetails, - ); - Navigator.of(context).pop(); - return; - } catch (e, s) { - _logger.severe("failed to check status", e, s); - showGenericErrorDialog(context: context, error: e).ignore(); - return; - } - await UserService.instance.onPassKeyVerified(context, response); - } - - Future _handleDeeplink(String? link) async { - if (!context.mounted || - Configuration.instance.hasConfiguredAccount() || - link == null) { - _logger.warning( - 'ignored deeplink: contextMounted ${context.mounted} hasConfiguredAccount ${Configuration.instance.hasConfiguredAccount()}', - ); - return; - } - try { - if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) { - if (Configuration.instance.isLoggedIn()) { - _logger.info('ignored deeplink: already configured'); - showToast(context, 'Account is already configured.'); - return; - } - final parsedUri = Uri.parse(link); - final sessionID = parsedUri.queryParameters['passkeySessionID']; - if (sessionID != widget.sessionID) { - showToast(context, "Session ID mismatch"); - _logger.warning('ignored deeplink: sessionID mismatch'); - return; - } - final String? authResponse = parsedUri.queryParameters['response']; - String base64String = authResponse!.toString(); - while (base64String.length % 4 != 0) { - base64String += '='; - } - final res = utf8.decode(base64.decode(base64String)); - final json = jsonDecode(res) as Map; - await UserService.instance.onPassKeyVerified(context, json); - } else { - _logger.info('ignored deeplink: $link mounted $mounted'); - } - } catch (e, s) { - _logger.severe('passKey: failed to handle deeplink', e, s); - showGenericErrorDialog(context: context, error: e).ignore(); - } - } - - Future _initDeepLinks() async { - final appLinks = AppLinks(); - // Attach a listener to the stream - appLinks.stringLinkStream.listen( - _handleDeeplink, - onError: (err) { - _logger.severe(err); - }, - ); - return false; - } - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - return Scaffold( - appBar: AppBar( - title: Text( - l10n.passkeyAuthTitle, - ), - ), - body: _getBody(), - ); - } - - Widget _getBody() { - return Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - context.l10n.waitingForVerification, - style: const TextStyle( - height: 1.4, - fontSize: 16, - ), - ), - const SizedBox(height: 16), - ButtonWidget( - buttonType: ButtonType.primary, - labelText: context.l10n.tryAgain, - onTap: () => launchPasskey(), - ), - const SizedBox(height: 16), - ButtonWidget( - buttonType: ButtonType.secondary, - labelText: context.l10n.checkStatus, - onTap: () async { - try { - await checkStatus(); - } catch (e) { - debugPrint('failed to check status %e'); - showGenericErrorDialog(context: context, error: e).ignore(); - } - }, - shouldSurfaceExecutionStates: true, - ), - const Padding(padding: EdgeInsets.all(30)), - if (widget.totp2FASessionID.isNotEmpty) - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - routeToPage( - context, - TwoFactorAuthenticationPage( - widget.totp2FASessionID, - ), - ); - }, - child: Container( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - context.l10n.loginWithTOTP, - style: const TextStyle( - decoration: TextDecoration.underline, - fontSize: 12, - ), - ), - ), - ), - ), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - UserService.instance.recoverTwoFactor( - context, - widget.sessionID, - TwoFactorType.passkey, - ); - }, - child: Container( - padding: const EdgeInsets.all(10), - child: Center( - child: Text( - context.l10n.recoverAccount, - style: const TextStyle( - decoration: TextDecoration.underline, - fontSize: 12, - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/mobile/apps/auth/lib/ui/settings/account_section_widget.dart b/mobile/apps/auth/lib/ui/settings/account_section_widget.dart index 568fd788dc..e72585e6de 100644 --- a/mobile/apps/auth/lib/ui/settings/account_section_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/account_section_widget.dart @@ -1,14 +1,15 @@ import 'package:ente_accounts/pages/change_email_dialog.dart'; import 'package:ente_accounts/pages/delete_account_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/account/recovery_key_page.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; +import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; @@ -81,8 +82,10 @@ class AccountSectionWidget extends StatelessWidget { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const PasswordEntryPage( - mode: PasswordEntryMode.update, + return PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.update, + const HomePage(), ); }, ),