From 17f5a7996ae326c496b02820a6708361ed2b1259 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta Date: Sun, 26 Nov 2023 16:27:25 +0530 Subject: [PATCH] Change Password: Confirm before signing out from other devices (#374) --- lib/l10n/arb/app_en.arb | 6 ++- lib/services/user_service.dart | 17 ++++---- lib/ui/account/password_entry_page.dart | 52 +++++++++++++++++++------ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 299c53f97..a4dda6b2f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -398,5 +398,9 @@ "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." }, "noInternetConnection": "No internet connection", - "pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again." + "pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.", + "signOutFromOtherDevices": "Sign out from other devices", + "signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.", + "signOutOtherDevices": "Sign out other devices", + "doNotSignOut": "Do not sign out" } diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index 0efed7eca..0bbbd81c7 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -441,6 +441,7 @@ class UserService { Future registerOrUpdateSrp( Uint8List loginKey, { SetKeysRequest? setKeysRequest, + bool logOutOtherDevices = false, }) async { try { final String username = const Uuid().v4().toString(); @@ -496,6 +497,7 @@ class UserService { 'setupID': setupSRPResponse.setupID, 'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)), 'updatedKeyAttr': setKeysRequest.toMap(), + 'logOutOtherDevices': logOutOtherDevices, }, ); } @@ -608,8 +610,9 @@ class UserService { Future updateKeyAttributes( KeyAttributes keyAttributes, - Uint8List loginKey, - ) async { + Uint8List loginKey, { + required bool logoutOtherDevices, + }) async { try { final setKeyRequest = SetKeysRequest( kekSalt: keyAttributes.kekSalt, @@ -618,11 +621,11 @@ class UserService { memLimit: keyAttributes.memLimit, opsLimit: keyAttributes.opsLimit, ); - await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest); - // await _enteDio.put( - // "/users/keys", - // data: setKeyRequest.toMap(), - // ); + await registerOrUpdateSrp( + loginKey, + setKeysRequest: setKeyRequest, + logOutOtherDevices: logoutOtherDevices, + ); await _config.setKeyAttributes(keyAttributes); } catch (e) { _logger.severe(e); diff --git a/lib/ui/account/password_entry_page.dart b/lib/ui/account/password_entry_page.dart index 5a66e865e..9b5879917 100644 --- a/lib/ui/account/password_entry_page.dart +++ b/lib/ui/account/password_entry_page.dart @@ -5,6 +5,7 @@ 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/common/web_page.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'; @@ -24,8 +25,7 @@ enum PasswordEntryMode { class PasswordEntryPage extends StatefulWidget { final PasswordEntryMode mode; - const PasswordEntryPage({required this.mode, Key? key}) - : super(key: key); + const PasswordEntryPage({required this.mode, Key? key}) : super(key: key); @override State createState() => _PasswordEntryPageState(); @@ -180,10 +180,11 @@ class _PasswordEntryPageState extends State { .copyWith(fontSize: 14), tags: { 'underline': StyledTextTag( - style: Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), + style: + Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), ), }, ), @@ -356,10 +357,11 @@ class _PasswordEntryPageState extends State { child: RichText( text: TextSpan( text: context.l10n.howItWorks, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), + style: + Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), ), ), ), @@ -374,13 +376,18 @@ class _PasswordEntryPageState extends State { } 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); + await UserService.instance.updateKeyAttributes( + result.item1, + result.item2, + logoutOtherDevices: logOutFromOthers, + ); await dialog.hide(); showShortToast(context, context.l10n.passwordChangedSuccessfully); Navigator.of(context).pop(); @@ -394,13 +401,34 @@ class _PasswordEntryPageState extends State { } } + 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) async { final l10n = context.l10n; final dialog = createProgressDialog(context, l10n.generatingEncryptionKeysTitle); await dialog.show(); try { - final KeyGenResult result = await Configuration.instance.generateKey(password); + final KeyGenResult result = + await Configuration.instance.generateKey(password); Configuration.instance.setVolatilePassword(null); await dialog.hide(); onDone() async {