2fa for unattended access

This commit is contained in:
rustdesk
2024-01-19 15:35:58 +08:00
parent 80857c22c9
commit 44e6b7dbb0
55 changed files with 673 additions and 60 deletions

View File

@@ -7,6 +7,7 @@ import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../common.dart';
import '../../models/model.dart';
@@ -1501,3 +1502,124 @@ void renameDialog(
);
});
}
void change2fa({Function()? callback}) async {
if (bind.mainHasValid2FaSync()) {
await bind.mainSetOption(key: "2fa", value: "");
callback?.call();
return;
}
var new2fa = (await bind.mainGenerate2Fa());
final secretRegex = RegExp(r'secret=([^&]+)');
final secret = secretRegex.firstMatch(new2fa)?.group(1);
var msg = ''.obs;
final RxString code = "".obs;
gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog(
title: Text(translate("enable-2fa-title")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(translate("enable-2fa-desc"),
style: TextStyle(fontSize: 12))
.marginOnly(bottom: 12),
SizedBox(
width: 160,
height: 160,
child: QrImageView(
backgroundColor: Colors.white,
data: new2fa,
version: QrVersions.auto,
size: 160,
gapless: false,
)).marginOnly(bottom: 6),
SelectableText(secret ?? '', style: TextStyle(fontSize: 12))
.marginOnly(bottom: 12),
Row(children: [
Expanded(
child: Obx(() => TextField(
decoration: InputDecoration(
errorText:
msg.value.isEmpty ? null : translate(msg.value),
hintText: translate("Verification code")),
onChanged: (value) {
code.value = value;
msg.value = '';
},
autofocus: true)))
]),
],
),
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
Obx(() => dialogButton(
"OK",
onPressed: code.value.trim().length == 6
? () async {
if (await bind.mainVerify2Fa(code: code.value.trim())) {
callback?.call();
close();
} else {
msg.value = translate('wrong-2fa-code');
}
}
: null,
)),
],
onCancel: close,
);
});
}
void enter2FaDialog(
SessionID sessionId, OverlayDialogManager dialogManager) async {
final RxString code = "".obs;
dialogManager.dismissAll();
dialogManager.show((setState, close, context) {
cancel() {
close();
closeConnection();
}
submit() {
if (code.value.trim().length != 6) {
return;
}
gFFI.send2FA(sessionId, code.value.trim());
close();
dialogManager.showLoading(translate('Logging in...'),
onCancel: closeConnection);
}
return CustomAlertDialog(
title: Text(translate("enter-2fa-title")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: translate("Verification code")),
onChanged: (value) {
code.value = value;
},
autofocus: true))
]),
],
),
onSubmit: submit,
onCancel: cancel,
actions: [
dialogButton(
'Cancel',
onPressed: cancel,
isOutline: true,
),
Obx(() => dialogButton(
'OK',
onPressed: code.value.trim().length == 6 ? submit : null,
)),
]);
});
}

View File

@@ -10,7 +10,6 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/plugin/manager.dart';
@@ -567,6 +566,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
child: Column(children: [
permissions(context),
password(context),
_Card(title: '2FA', children: [tfa()]),
_Card(title: 'ID', children: [changeId()]),
more(context),
]),
@@ -575,6 +575,45 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
)).marginOnly(bottom: _kListViewBottomMargin));
}
Widget tfa() {
bool enabled = !locked;
// Simple temp wrapper for PR check
tmpWrapper() {
RxBool has2fa = bind.mainHasValid2FaSync().obs;
update() async {
has2fa.value = bind.mainHasValid2FaSync();
}
onChanged(bool? checked) async {
change2fa(callback: update);
}
return GestureDetector(
child: InkWell(
child: Obx(() => Row(
children: [
Checkbox(
value: has2fa.value,
onChanged: enabled ? onChanged : null)
.marginOnly(right: 5),
Expanded(
child: Text(
translate('enable-2fa-title'),
style:
TextStyle(color: _disabledTextColor(context, enabled)),
))
],
)),
),
onTap: () {
onChanged(!has2fa.value);
},
).marginOnly(left: _kCheckBoxLeftMargin);
}
return tmpWrapper();
}
Widget changeId() {
return ChangeNotifierProvider.value(
value: gFFI.serverModel,

View File

@@ -220,6 +220,16 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
Provider.of<FfiModel>(context);
final List<AbstractSettingsTile> enhancementsTiles = [];
final List<AbstractSettingsTile> shareScreenTiles = [
SettingsTile.switchTile(
title: Text(translate('enable-2fa-title')),
initialValue: bind.mainHasValid2FaSync(),
onToggle: (_) async {
update() async {
setState(() {});
}
change2fa(callback: update);
},
),
SettingsTile.switchTile(
title: Text(translate('Deny LAN discovery')),
initialValue: _denyLANDiscovery,

View File

@@ -498,6 +498,8 @@ class FfiModel with ChangeNotifier {
final link = evt['link'];
if (type == 're-input-password') {
wrongPasswordDialog(sessionId, dialogManager, type, title, text);
} else if (type == 'input-2fa') {
enter2FaDialog(sessionId, dialogManager);
} else if (type == 'input-password') {
enterPasswordDialog(sessionId, dialogManager);
} else if (type == 'session-login' || type == 'session-re-login') {
@@ -2303,6 +2305,10 @@ class FFI {
remember: remember);
}
void send2FA(SessionID sessionId, String code) {
bind.sessionSend2Fa(sessionId: sessionId, code: code);
}
/// Close the remote session.
Future<void> close({bool closeSession = true}) async {
closed = true;