Skip to content

Commit

Permalink
Merge pull request #758 from threefoldtech/development_delete_user
Browse files Browse the repository at this point in the history
Support deleting account
  • Loading branch information
AhmedHanafy725 authored Nov 18, 2024
2 parents 60419bc + 2fd0a73 commit 1717d76
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 77 deletions.
183 changes: 118 additions & 65 deletions app/lib/screens/preference_screen.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pkid/flutter_pkid.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:threebotlogin/app_config.dart';
import 'package:threebotlogin/apps/free_flow_pages/ffp_events.dart';
Expand All @@ -15,10 +19,14 @@ import 'package:threebotlogin/screens/authentication_screen.dart';
import 'package:threebotlogin/screens/change_pin_screen.dart';
import 'package:threebotlogin/screens/main_screen.dart';
import 'package:threebotlogin/services/fingerprint_service.dart';
import 'package:threebotlogin/services/open_kyc_service.dart';
import 'package:threebotlogin/services/pkid_service.dart';
import 'package:threebotlogin/services/shared_preference_service.dart';
import 'package:threebotlogin/services/wallet_service.dart';
import 'package:threebotlogin/widgets/custom_dialog.dart';
import 'package:threebotlogin/widgets/layout_drawer.dart';
import 'package:threebotlogin/providers/theme_provider.dart';
import 'package:threebotlogin/widgets/wallets/warning_dialog.dart';
import 'package:url_launcher/url_launcher.dart';

class PreferenceScreen extends ConsumerStatefulWidget {
Expand Down Expand Up @@ -47,6 +55,7 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
Globals globals = Globals();

MaterialColor thiscolor = Colors.green;
bool deleteLoading = false;

@override
void initState() {
Expand Down Expand Up @@ -164,7 +173,7 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
}),
ListTile(
leading: const Icon(Icons.lock),
title: const Text('Change pincode'),
title: const Text('Change PIN'),
onTap: () async {
_changePincode();
},
Expand Down Expand Up @@ -241,25 +250,37 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
title: const Text('Terms and conditions'),
onTap: () async => {await _showTermsAndConds()},
),
ListTile(
leading: const Icon(Icons.logout_outlined),
title: Text(
'Log Out',
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(color: Theme.of(context).colorScheme.onSurface),
),
onTap: _showDialog,
),
ExpansionTile(
title: const Text(
'Advanced settings',
),
children: <Widget>[
ListTile(
leading: const Icon(Icons.person),
leading: Icon(
Icons.remove_circle,
color: Theme.of(context).colorScheme.error,
),
title: Text(
'Remove Account From Device',
'Delete Account',
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(color: Theme.of(context).colorScheme.error),
),
trailing: Icon(
Icons.remove_circle,
color: Theme.of(context).colorScheme.error,
),
onTap: _showDialog,
onTap: () {
_showDialog(delete: true);
},
),
],
),
Expand Down Expand Up @@ -311,67 +332,99 @@ class _PreferenceScreenState extends ConsumerState<PreferenceScreen> {
);
}

void _showDialog() {
void _showDialog({delete = false}) {
String title = 'Log Out';
String message = 'Are you sure you want to log out?';
if (delete) {
title = 'Are you sure?';
message =
"If you confirm, your account will be deleted. You won't be able to recover your account.";
}
preferenceContext = context;
showDialog(
context: context,
builder: (BuildContext context) => CustomDialog(
type: DialogType.Warning,
image: Icons.warning,
title: 'Are you sure?',
description:
'If you confirm, your account will be removed from this device. You can always recover your account with your username and phrase.',
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text(
'Yes',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Theme.of(context).colorScheme.warning),
),
onPressed: () async {
// try {
// String deviceID = await _listener.getToken();
// removeDeviceId(deviceID);
// } catch (e) {}
Events().emit(CloseSocketEvent());
Events().emit(FfpClearCacheEvent());
bool result = await clearData();
if (result) {
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MainScreen(
initDone: true, registered: false)));
} else {
showDialog(
context: preferenceContext!,
builder: (BuildContext context) => CustomDialog(
type: DialogType.Error,
title: 'Error',
description:
'Something went wrong when trying to remove your account.',
actions: <Widget>[
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop(context);
},
)
],
builder: (BuildContext context) => WarningDialogWidget(
title: title,
description: message,
onAgree: () async {
deleteLoading = true;
setState(() {});
if (delete) {
String? pin = await getPin();
bool? authenticated = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AuthenticationScreen(
correctPin: pin!,
userMessage: 'Please enter your PIN code',
),
);
));

if (authenticated == null || !authenticated) {
deleteLoading = false;
setState(() {});
return false;
}
}
Events().emit(CloseSocketEvent());
Events().emit(FfpClearCacheEvent());
bool deleted = true;
if (delete) {
try {
Response response = await deleteUser();
if (response.statusCode != HttpStatus.noContent) {
deleted = false;
}
},
),
],
} catch (e) {
print('Failed to delete user due to $e');
deleted = false;
}
if (deleted) {
final seedPhrase = await getPhrase();
FlutterPkid client = await getPkidClient(seedPhrase: seedPhrase!);
await client.setPKidDoc('email', '');
await client.setPKidDoc('phone', '');
await saveWalletsToPkid([]);
}
}
bool result = false;
if (deleted) {
result = await clearData();
if (result) {
Navigator.pop(context);
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
const MainScreen(initDone: true, registered: false)));
}
}
if (!result || !deleted) {
await showDialog(
context: preferenceContext!,
builder: (BuildContext context) => CustomDialog(
type: DialogType.Error,
title: 'Error',
description:
'Something went wrong when trying to remove your account.',
actions: <Widget>[
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop(context);
},
)
],
),
);
deleteLoading = false;
setState(() {});
return false;
}
deleteLoading = false;
setState(() {});
return true;
},
),
);
}
Expand Down
4 changes: 2 additions & 2 deletions app/lib/screens/recover_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class _RecoverScreenState extends State<RecoverScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Recover Account'),
title: const Text('Log In'),
),
body: Container(
padding: const EdgeInsets.all(20.0),
Expand Down Expand Up @@ -189,7 +189,7 @@ class _RecoverScreenState extends State<RecoverScreen> {
ElevatedButton(
style: ElevatedButton.styleFrom(),
child: Text(
'Recover Account',
'Log In',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer),
),
Expand Down
8 changes: 4 additions & 4 deletions app/lib/screens/unregistered_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
context: context,
builder: (BuildContext context) => const CustomDialog(
image: Icons.check,
title: 'Recovered',
description: 'Your account has been recovered.',
title: 'Logged In',
description: 'You have logged In successfully.',
),
);
await Future.delayed(
Expand Down Expand Up @@ -101,7 +101,7 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Sign Up',
'Create Account',
style: Theme.of(context)
.textTheme
.titleMedium!
Expand Down Expand Up @@ -129,7 +129,7 @@ class _UnregisteredScreenState extends State<UnregisteredScreen>
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Recover Account',
'Log In',
style: Theme.of(context)
.textTheme
.titleMedium!
Expand Down
24 changes: 23 additions & 1 deletion app/lib/services/open_kyc_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Future<Response> sendVerificationSms() async {
return http.post(url, body: encodedBody, headers: requestHeaders);
}

// TODO: Remove this method and user update user data
// TODO: Remove this method and use update user data
Future<Response> updateEmailAddressOfUser() async {
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
Uint8List sk = await getPrivateKey();
Expand Down Expand Up @@ -155,3 +155,25 @@ Future<Response> updateUserData(String field, String value) async {

return http.post(url, headers: loginRequestHeaders, body: encodedBody);
}

Future<Response> deleteUser() async {
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
Uint8List sk = await getPrivateKey();

Map<String, String> payload = {
'timestamp': timestamp,
'intention': 'delete-user'
};
String signedPayload = await signData(jsonEncode(payload), sk);

Map<String, String> loginRequestHeaders = {
'Content-type': 'application/json',
'Jimber-Authorization': signedPayload
};

final doubleName = await getDoubleName();
Uri url = Uri.parse('$threeBotApiUrl/users/$doubleName');
logger.i('Sending call: ${url.toString()}');

return http.delete(url, headers: loginRequestHeaders);
}
8 changes: 4 additions & 4 deletions app/lib/services/wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Future<void> addWallet(String walletName, String walletSecret,
seed: walletSecret,
type: type));

await _saveWalletsToPkid(wallets);
await saveWalletsToPkid(wallets);
}

Future<void> editWallet(String oldName, String newName) async {
Expand All @@ -132,16 +132,16 @@ Future<void> editWallet(String oldName, String newName) async {
break;
}
}
await _saveWalletsToPkid(wallets);
await saveWalletsToPkid(wallets);
}

Future<void> deleteWallet(String walletName) async {
List<PkidWallet> wallets = await _getPkidWallets();
wallets = wallets.where((w) => w.name != walletName).toList();
await _saveWalletsToPkid(wallets);
await saveWalletsToPkid(wallets);
}

Future<void> _saveWalletsToPkid(List<PkidWallet> wallets) async {
Future<void> saveWalletsToPkid(List<PkidWallet> wallets) async {
FlutterPkid client = await _getPkidClient();
final encodedWallets = json.encode(wallets.map((w) => w.toMap()).toList());
await client.setPKidDoc('purse', encodedWallets);
Expand Down
8 changes: 8 additions & 0 deletions backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,14 @@ def update_user(double_name, field, value):
except Error as e:
print(e)

def delete_user(double_name):
delete_sql = f'DELETE FROM users WHERE double_name =?'
try:
cursor = conn.cursor()
cursor.execute(delete_sql, (double_name,))
conn.commit()
except Error as e:
print(e)


def create_db(conn):
Expand Down
2 changes: 1 addition & 1 deletion backend/routes/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
@api_misc.route("/minimumversion", methods=["get"])
def minimum_version_handler():
response = Response(
response=json.dumps({"android": 182, "ios": 182}), mimetype="application/json"
response=json.dumps({"android": 178, "ios": 178}), mimetype="application/json"
)
return response

Expand Down
Loading

0 comments on commit 1717d76

Please sign in to comment.