From 6672c242f2b2168306a0a557faa7e49de763194b Mon Sep 17 00:00:00 2001 From: rustdesk Date: Mon, 28 Mar 2022 15:44:45 +0800 Subject: [PATCH] login --- lib/main.dart | 2 + lib/models/model.dart | 11 +- lib/pages/connection_page.dart | 83 ++++++---- lib/pages/settings_page.dart | 276 ++++++++++++++++++++++++++++++++- 4 files changed, 334 insertions(+), 38 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 32b8cdf8a..9d06e7d76 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'common.dart'; import 'models/model.dart'; import 'pages/home_page.dart'; import 'pages/server_page.dart'; +import 'pages/settings_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -36,6 +37,7 @@ class App extends StatelessWidget { providers.add(ChangeNotifierProvider.value(value: FFI.serverModel)); } } + refreshCurrentUser(); return MultiProvider( providers: providers, child: MaterialApp( diff --git a/lib/models/model.dart b/lib/models/model.dart index 1df5b820a..4105c725d 100644 --- a/lib/models/model.dart +++ b/lib/models/model.dart @@ -65,6 +65,10 @@ class FfiModel with ChangeNotifier { notifyListeners(); } + void updateUser() { + notifyListeners(); + } + bool keyboard() => _permissions['keyboard'] != false; void clear() { @@ -103,7 +107,7 @@ class FfiModel with ChangeNotifier { _permissions.clear(); } - void update(String peerId,HandleMsgBox handleMsgBox) { + void update(String peerId, HandleMsgBox handleMsgBox) { var pos; for (;;) { var evt = FFI.popEvent(); @@ -130,9 +134,10 @@ class FfiModel with ChangeNotifier { } else if (name == 'permission') { FFI.ffiModel.updatePermission(evt); } else if (name == 'chat_client_mode') { - FFI.chatModel.receive(ChatModel.clientModeID,evt['text'] ?? ""); + FFI.chatModel.receive(ChatModel.clientModeID, evt['text'] ?? ""); } else if (name == 'chat_server_mode') { - FFI.chatModel.receive(int.parse(evt['id'] as String),evt['text'] ?? ""); + FFI.chatModel + .receive(int.parse(evt['id'] as String), evt['text'] ?? ""); } else if (name == 'file_dir') { FFI.fileModel.receiveFileDir(evt); } else if (name == 'job_progress') { diff --git a/lib/pages/connection_page.dart b/lib/pages/connection_page.dart index a38618c3b..c092b7d04 100644 --- a/lib/pages/connection_page.dart +++ b/lib/pages/connection_page.dart @@ -19,29 +19,7 @@ class ConnectionPage extends StatefulWidget implements PageShape { final title = translate("Connection"); @override - final appBarActions = isWeb - ? [ - PopupMenuButton(itemBuilder: (context) { - return [ - PopupMenuItem( - child: Text(translate('ID/Relay Server')), - value: "server", - ), - PopupMenuItem( - child: Text(translate('About') + ' RustDesk'), - value: "about", - ) - ]; - }, onSelected: (value) { - if (value == 'server') { - showServer(); - } - if (value == 'about') { - showAbout(); - } - }), - ] - : []; + final appBarActions = isWeb ? [WebMenu()] : []; @override _ConnectionPageState createState() => _ConnectionPageState(); @@ -276,11 +254,15 @@ class _ConnectionPageState extends State { context: context, position: this._menuPos, items: [ - PopupMenuItem( - child: Text(translate('Remove')), value: 'remove'), - PopupMenuItem( - child: Text(translate('File transfer')), value: 'file'), - ], + PopupMenuItem( + child: Text(translate('Remove')), value: 'remove') + ] + + (isWeb + ? [] + : [ + PopupMenuItem( + child: Text(translate('File transfer')), value: 'file') + ]), elevation: 8, ); if (value == 'remove') { @@ -293,3 +275,48 @@ class _ConnectionPageState extends State { } } } + +class WebMenu extends StatefulWidget { + @override + _WebMenuState createState() => _WebMenuState(); +} + +class _WebMenuState extends State { + @override + Widget build(BuildContext context) { + Provider.of(context); + final username = getUsername(); + return PopupMenuButton(itemBuilder: (context) { + return [ + PopupMenuItem( + child: Text(translate('ID/Relay Server')), + value: "server", + ), + PopupMenuItem( + child: Text(username == null + ? translate("Login") + : translate("Logout") + ' ($username)'), + value: "login", + ), + PopupMenuItem( + child: Text(translate('About') + ' RustDesk'), + value: "about", + ) + ]; + }, onSelected: (value) { + if (value == 'server') { + showServer(); + } + if (value == 'about') { + showAbout(); + } + if (value == 'login') { + if (username == null) { + showLogin(); + } else { + logout(); + } + } + }); + } +} diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 93d4f6420..49b18ce8f 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,11 +1,16 @@ +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:provider/provider.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; import '../common.dart'; +import '../widgets/dialog.dart'; import '../models/model.dart'; import 'home_page.dart'; -class SettingsPage extends StatelessWidget implements PageShape { +class SettingsPage extends StatefulWidget implements PageShape { @override final title = translate("Settings"); @@ -15,14 +20,39 @@ class SettingsPage extends StatelessWidget implements PageShape { @override final appBarActions = []; + @override + _SettingsState createState() => _SettingsState(); +} + +class _SettingsState extends State { static const url = 'https://rustdesk.com/'; @override Widget build(BuildContext context) { + Provider.of(context); + final username = getUsername(); return SettingsList( sections: [ SettingsSection( - title: Text("Common"), + title: Text(""), + tiles: [ + SettingsTile.navigation( + title: Text(username == null + ? translate("Login") + : translate("Logout") + ' ($username)'), + leading: Icon(Icons.person), + onPressed: (context) { + if (username == null) { + showLogin(); + } else { + logout(); + } + }, + ), + ], + ), + SettingsSection( + title: Text(translate("Settings")), tiles: [ SettingsTile.navigation( title: Text(translate('ID/Relay Server')), @@ -34,20 +64,19 @@ class SettingsPage extends StatelessWidget implements PageShape { ], ), SettingsSection( - title: Text("About"), + title: Text(translate("About")), tiles: [ SettingsTile.navigation( - title: Text("Version: " + version), + title: Text(translate("Version: ") + version), value: InkWell( onTap: () async { - const url = 'https://rustdesk.com/'; if (await canLaunch(url)) { await launch(url); } }, child: Padding( padding: EdgeInsets.symmetric(vertical: 8), - child: Text('Support', + child: Text('rustdesk.com', style: TextStyle( decoration: TextDecoration.underline, )), @@ -65,10 +94,12 @@ void showServer() { final formKey = GlobalKey(); final id0 = FFI.getByName('option', 'custom-rendezvous-server'); final relay0 = FFI.getByName('option', 'relay-server'); + final api0 = FFI.getByName('option', 'api-server'); final key0 = FFI.getByName('option', 'key'); var id = ''; var relay = ''; var key = ''; + var api = ''; DialogManager.show((setState, close) { return CustomAlertDialog( title: Text(translate('ID/Relay Server')), @@ -103,6 +134,16 @@ void showServer() { ] : []) + [ + TextFormField( + initialValue: api0, + decoration: InputDecoration( + labelText: translate('API Server'), + ), + validator: validate, + onSaved: (String? value) { + if (value != null) api = value.trim(); + }, + ), TextFormField( initialValue: key0, decoration: InputDecoration( @@ -136,6 +177,9 @@ void showServer() { 'option', '{"name": "relay-server", "value": "$relay"}'); if (key != key0) FFI.setByName('option', '{"name": "key", "value": "$key"}'); + if (api != api0) + FFI.setByName( + 'option', '{"name": "api-server", "value": "$api"}'); close(); } }, @@ -173,7 +217,7 @@ void showAbout() { }, child: Padding( padding: EdgeInsets.symmetric(vertical: 8), - child: Text('Support', + child: Text('rustdesk.com', style: TextStyle( decoration: TextDecoration.underline, )), @@ -186,3 +230,221 @@ void showAbout() { ); }, barrierDismissible: true); } + +Future login(String name, String pass) async { +/* js test CORS +const data = { username: 'example', password: 'xx' }; + +fetch('http://localhost:21114/api/login', { + method: 'POST', // or 'PUT' + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), +}) +.then(response => response.json()) +.then(data => { + console.log('Success:', data); +}) +.catch((error) => { + console.error('Error:', error); +}); +*/ + final url = getUrl(); + final body = { + 'username': name, + 'password': pass, + 'id': FFI.getByName('server_id'), + 'uuid': FFI.getByName('uuid') + }; + try { + final response = await http.post(Uri.parse('${url}/api/login'), + headers: {"Content-Type": "application/json"}, body: json.encode(body)); + return parseResp(response.body); + } catch (e) { + print(e); + return 'Failed to access $url'; + } +} + +String parseResp(String body) { + final data = json.decode(body); + final error = data['error']; + if (error != null) { + return error!; + } + final token = data['access_token']; + if (token != null) { + FFI.setByName('option', '{"name": "access_token", "value": "$token"}'); + } + final info = data['user']; + if (info != null) { + final value = json.encode(info); + FFI.setByName('option', json.encode({"name": "user_info", "value": value})); + FFI.ffiModel.updateUser(); + } + return ''; +} + +void refreshCurrentUser() async { + final token = FFI.getByName("option", "access_token"); + if (token == '') return; + final url = getUrl(); + final body = { + 'id': FFI.getByName('server_id'), + 'uuid': FFI.getByName('uuid') + }; + try { + final response = await http.post(Uri.parse('${url}/api/currentUser'), + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer $token" + }, + body: json.encode(body)); + final status = response.statusCode; + if (status == 401 || status == 400) { + resetToken(); + return; + } + parseResp(response.body); + } catch (e) { + print('$e'); + } +} + +void logout() async { + final token = FFI.getByName("option", "access_token"); + if (token == '') return; + final url = getUrl(); + final body = { + 'id': FFI.getByName('server_id'), + 'uuid': FFI.getByName('uuid') + }; + try { + await http.post(Uri.parse('${url}/api/logout'), + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer $token" + }, + body: json.encode(body)); + } catch (e) { + EasyLoading.showToast('Failed to access $url', + maskType: EasyLoadingMaskType.black); + } + resetToken(); +} + +void resetToken() { + FFI.setByName('option', '{"name": "access_token", "value": ""}'); + FFI.setByName('option', '{"name": "user_info", "value": ""}'); + FFI.ffiModel.updateUser(); +} + +String getUrl() { + var url = FFI.getByName('option', 'api-server'); + if (url == '') { + url = FFI.getByName('option', 'custom-rendezvous-server'); + if (url != '') { + if (url.contains(':')) { + final tmp = url.split(':'); + if (tmp.length == 2) { + var port = int.parse(tmp[1]) - 2; + url = 'http://${tmp[0]}:$port'; + } + } else { + url = 'http://${url}:21114'; + } + } + } + if (url == '') { + url = 'https://admin.rustdesk.com'; + } + return url; +} + +void showLogin() { + final passwordController = TextEditingController(); + final nameController = TextEditingController(); + var loading = false; + var error = ''; + DialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate('Login')), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + TextField( + autofocus: true, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.visiblePassword, + decoration: InputDecoration( + labelText: translate('Username'), + ), + controller: nameController, + ), + PasswordWidget(controller: passwordController), + ]), + actions: (loading + ? [CircularProgressIndicator()] + : (error != "" + ? [ + Text(translate(error), + style: TextStyle(color: Colors.red)) + ] + : [])) + + [ + TextButton( + style: flatButtonStyle, + onPressed: loading + ? null + : () { + close(); + setState(() { + loading = false; + }); + }, + child: Text(translate('Cancel')), + ), + TextButton( + style: flatButtonStyle, + onPressed: loading + ? null + : () async { + final name = nameController.text.trim(); + final pass = passwordController.text.trim(); + if (name != "" && pass != "") { + setState(() { + loading = true; + }); + final e = await login(name, pass); + setState(() { + loading = false; + error = e; + }); + if (e == "") { + close(); + } + } + }, + child: Text(translate('OK')), + ), + ], + ); + }); +} + +String? getUsername() { + final token = FFI.getByName("option", "access_token"); + String? username; + if (token != "") { + final info = FFI.getByName("option", "user_info"); + if (info != "") { + try { + Map tmp = json.decode(info); + username = tmp["name"]; + } catch (e) { + print('$e'); + } + } + } + return username; +}