Change Password: Confirm before signing out from other devices (#374)

This commit is contained in:
Neeraj Gupta 2023-11-26 16:27:25 +05:30 committed by GitHub
parent cf6b4f5423
commit 17f5a7996a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 20 deletions

View file

@ -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." "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", "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"
} }

View file

@ -441,6 +441,7 @@ class UserService {
Future<void> registerOrUpdateSrp( Future<void> registerOrUpdateSrp(
Uint8List loginKey, { Uint8List loginKey, {
SetKeysRequest? setKeysRequest, SetKeysRequest? setKeysRequest,
bool logOutOtherDevices = false,
}) async { }) async {
try { try {
final String username = const Uuid().v4().toString(); final String username = const Uuid().v4().toString();
@ -496,6 +497,7 @@ class UserService {
'setupID': setupSRPResponse.setupID, 'setupID': setupSRPResponse.setupID,
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)), 'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
'updatedKeyAttr': setKeysRequest.toMap(), 'updatedKeyAttr': setKeysRequest.toMap(),
'logOutOtherDevices': logOutOtherDevices,
}, },
); );
} }
@ -608,8 +610,9 @@ class UserService {
Future<void> updateKeyAttributes( Future<void> updateKeyAttributes(
KeyAttributes keyAttributes, KeyAttributes keyAttributes,
Uint8List loginKey, Uint8List loginKey, {
) async { required bool logoutOtherDevices,
}) async {
try { try {
final setKeyRequest = SetKeysRequest( final setKeyRequest = SetKeysRequest(
kekSalt: keyAttributes.kekSalt, kekSalt: keyAttributes.kekSalt,
@ -618,11 +621,11 @@ class UserService {
memLimit: keyAttributes.memLimit, memLimit: keyAttributes.memLimit,
opsLimit: keyAttributes.opsLimit, opsLimit: keyAttributes.opsLimit,
); );
await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest); await registerOrUpdateSrp(
// await _enteDio.put( loginKey,
// "/users/keys", setKeysRequest: setKeyRequest,
// data: setKeyRequest.toMap(), logOutOtherDevices: logoutOtherDevices,
// ); );
await _config.setKeyAttributes(keyAttributes); await _config.setKeyAttributes(keyAttributes);
} catch (e) { } catch (e) {
_logger.severe(e); _logger.severe(e);

View file

@ -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/account/recovery_key_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/ui/common/web_page.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/ui/home_page.dart';
import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/navigation_util.dart';
@ -24,8 +25,7 @@ enum PasswordEntryMode {
class PasswordEntryPage extends StatefulWidget { class PasswordEntryPage extends StatefulWidget {
final PasswordEntryMode mode; final PasswordEntryMode mode;
const PasswordEntryPage({required this.mode, Key? key}) const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
: super(key: key);
@override @override
State<PasswordEntryPage> createState() => _PasswordEntryPageState(); State<PasswordEntryPage> createState() => _PasswordEntryPageState();
@ -180,10 +180,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
.copyWith(fontSize: 14), .copyWith(fontSize: 14),
tags: { tags: {
'underline': StyledTextTag( 'underline': StyledTextTag(
style: Theme.of(context).textTheme.titleMedium!.copyWith( style:
fontSize: 14, Theme.of(context).textTheme.titleMedium!.copyWith(
decoration: TextDecoration.underline, fontSize: 14,
), decoration: TextDecoration.underline,
),
), ),
}, },
), ),
@ -356,10 +357,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: context.l10n.howItWorks, text: context.l10n.howItWorks,
style: Theme.of(context).textTheme.titleMedium!.copyWith( style:
fontSize: 14, Theme.of(context).textTheme.titleMedium!.copyWith(
decoration: TextDecoration.underline, fontSize: 14,
), decoration: TextDecoration.underline,
),
), ),
), ),
), ),
@ -374,13 +376,18 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} }
void _updatePassword() async { void _updatePassword() async {
final logOutFromOthers = await logOutFromOtherDevices(context);
final dialog = final dialog =
createProgressDialog(context, context.l10n.generatingEncryptionKeys); createProgressDialog(context, context.l10n.generatingEncryptionKeys);
await dialog.show(); await dialog.show();
try { try {
final result = await Configuration.instance final result = await Configuration.instance
.getAttributesForNewPassword(_passwordController1.text); .getAttributesForNewPassword(_passwordController1.text);
await UserService.instance.updateKeyAttributes(result.item1, result.item2); await UserService.instance.updateKeyAttributes(
result.item1,
result.item2,
logoutOtherDevices: logOutFromOthers,
);
await dialog.hide(); await dialog.hide();
showShortToast(context, context.l10n.passwordChangedSuccessfully); showShortToast(context, context.l10n.passwordChangedSuccessfully);
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -394,13 +401,34 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} }
} }
Future<bool> 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<void> _showRecoveryCodeDialog(String password) async { Future<void> _showRecoveryCodeDialog(String password) async {
final l10n = context.l10n; final l10n = context.l10n;
final dialog = final dialog =
createProgressDialog(context, l10n.generatingEncryptionKeysTitle); createProgressDialog(context, l10n.generatingEncryptionKeysTitle);
await dialog.show(); await dialog.show();
try { try {
final KeyGenResult result = await Configuration.instance.generateKey(password); final KeyGenResult result =
await Configuration.instance.generateKey(password);
Configuration.instance.setVolatilePassword(null); Configuration.instance.setVolatilePassword(null);
await dialog.hide(); await dialog.hide();
onDone() async { onDone() async {