30a19fcc1e
Added: - changePassword to change the password NOTE: this requires the old password, just to prevent account hijacking. - some basic user limit information - theme selector NOTE: the system theme is meant to function like auto-theme, and is directly translated into a flutter ThemeMode, however, this does not appear to be working on the web. This commit also adds the logout and delete account buttons, but they do not yet delete all rooms, nor do they properly logout the user. BUG: User is not logged out correctly, reloading the page fixes this. Maybe localstore.listen does not detect deletion?
177 lines
5.6 KiB
Dart
177 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:outbag_app/backend/crypto.dart';
|
|
import 'package:outbag_app/backend/request.dart';
|
|
import 'package:outbag_app/backend/user.dart';
|
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
|
import 'package:outbag_app/tools/snackbar.dart';
|
|
import 'package:routemaster/routemaster.dart';
|
|
|
|
class ChangePasswordDialog extends StatefulWidget {
|
|
const ChangePasswordDialog({super.key});
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _ChangePasswordDialogState();
|
|
}
|
|
|
|
class _ChangePasswordDialogState extends State<ChangePasswordDialog> {
|
|
final TextEditingController _ctrOldPassword = TextEditingController();
|
|
final TextEditingController _ctrNewPassword = TextEditingController();
|
|
final TextEditingController _ctrNewPasswordRepeat = TextEditingController();
|
|
|
|
User? user;
|
|
|
|
void loadUser() async {
|
|
final rmaster = Routemaster.of(context);
|
|
try {
|
|
final u = await User.fromDisk();
|
|
setState(() {
|
|
user = u;
|
|
});
|
|
} catch (_) {
|
|
// logout user
|
|
await User.removeDisk();
|
|
// move to welcome screen
|
|
rmaster.replace('/');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
User.listen((data) async {
|
|
try {
|
|
final u = await User.fromDisk();
|
|
setState(() {
|
|
user = u;
|
|
});
|
|
} catch (_) {}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AlertDialog(
|
|
title: const Text('Change Password'),
|
|
icon: const Icon(Icons.password),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: TextField(
|
|
controller: _ctrOldPassword,
|
|
keyboardType: TextInputType.visiblePassword,
|
|
obscureText: true,
|
|
decoration: const InputDecoration(
|
|
prefixIcon: Icon(Icons.lock),
|
|
labelText: 'Old Password',
|
|
hintText: 'Your current password',
|
|
helperText:
|
|
'For safety, you have to type your current passwort',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: TextField(
|
|
controller: _ctrNewPassword,
|
|
keyboardType: TextInputType.visiblePassword,
|
|
obscureText: true,
|
|
decoration: const InputDecoration(
|
|
prefixIcon: Icon(Icons.lock),
|
|
labelText: 'New Password',
|
|
hintText: 'Your new password',
|
|
helperText: 'Password have to be at least six characters long',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: TextField(
|
|
controller: _ctrNewPasswordRepeat,
|
|
keyboardType: TextInputType.visiblePassword,
|
|
obscureText: true,
|
|
decoration: const InputDecoration(
|
|
prefixIcon: Icon(Icons.lock),
|
|
labelText: 'Repeat new Password',
|
|
hintText: 'Type your new password again',
|
|
helperText:
|
|
'Type your new password again, to make sure you know it',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
)),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
// close popup
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: const Text('Cancel'),
|
|
),
|
|
FilledButton(
|
|
onPressed: () async {
|
|
final scaffMgr = ScaffoldMessenger.of(context);
|
|
final nav = Navigator.of(context);
|
|
|
|
// validate password
|
|
if (_ctrNewPassword.text.length < 6) {
|
|
// password has to be at least 6 characters long
|
|
showSimpleSnackbar(scaffMgr,
|
|
text: 'Password has to be at least 6 characters longs',
|
|
action: 'Dismiss');
|
|
|
|
_ctrNewPasswordRepeat.clear();
|
|
return;
|
|
}
|
|
if (_ctrNewPassword.text != _ctrNewPasswordRepeat.text) {
|
|
// new passwords do not match
|
|
showSimpleSnackbar(scaffMgr,
|
|
text: 'New passwords do not match', action: 'Dismiss');
|
|
|
|
_ctrNewPasswordRepeat.clear();
|
|
return;
|
|
}
|
|
if (hashPassword(_ctrOldPassword.text) != user?.password) {
|
|
// current password wrong
|
|
showSimpleSnackbar(scaffMgr,
|
|
text: 'Old password is wrong', action: 'Dismiss');
|
|
|
|
_ctrOldPassword.clear();
|
|
return;
|
|
}
|
|
|
|
final password = hashPassword(_ctrNewPassword.text);
|
|
|
|
// send request
|
|
doNetworkRequest(scaffMgr,
|
|
needUser: false,
|
|
req: (_) => postWithCreadentials(
|
|
path: 'changePassword',
|
|
target: (user?.server)!,
|
|
body: {'accountKey': password},
|
|
credentials: user!),
|
|
onOK: (_) async {
|
|
// update local user struct
|
|
final updatedUser = User(
|
|
username: (user?.username)!,
|
|
password: password,
|
|
server: (user?.server)!);
|
|
await updatedUser.toDisk();
|
|
},
|
|
after: () {
|
|
// close popup
|
|
nav.pop();
|
|
});
|
|
},
|
|
child: const Text('Change password'),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
}
|