From 4293d010ef1f73fb01af233574eedf750367ef9d Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 2 Sep 2023 10:52:57 +0800 Subject: [PATCH] Update code --- lib/auth.dart | 2 +- lib/create_root_user.dart | 230 ++++++++++++++++++++++++++++++++++++++ lib/globals.dart | 38 ++++++- lib/home.dart | 41 +++---- lib/l10n/app_en.arb | 5 +- lib/l10n/app_zh.arb | 5 +- lib/login.dart | 36 +++--- lib/main.dart | 9 +- lib/set_server.dart | 8 +- 9 files changed, 322 insertions(+), 52 deletions(-) create mode 100644 lib/create_root_user.dart diff --git a/lib/auth.dart b/lib/auth.dart index 1de1018..4fa1a83 100644 --- a/lib/auth.dart +++ b/lib/auth.dart @@ -32,7 +32,7 @@ class AuthInfo { final u = _user!; _log.info( "Logged in as ${u.username} (${u.id}). isAdmin: ${u.isAdmin}. permissions: ${u.permissions}"); - } else if (re.status == 401) { + } else if (re.status == 401 || re.status == 1 || re.status == 404) { _user = null; } else { _user = null; diff --git a/lib/create_root_user.dart b/lib/create_root_user.dart new file mode 100644 index 0000000..d5429d2 --- /dev/null +++ b/lib/create_root_user.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; +import 'globals.dart'; +import 'login.dart'; + +final _log = Logger("CreateRootUserPage"); + +class CreateRootUserPage extends StatefulWidget { + const CreateRootUserPage({Key? key}) : super(key: key); + + static const String routeName = '/create_root_user'; + + @override + State createState() => _CreateRootUserPage(); +} + +class _CreateRootUserPage extends State + with ThemeModeWidget { + final _formKey = GlobalKey(); + bool _createdUser = false; + String _username = ""; + String _password = ""; + bool _passwordVisible = false; + bool _isValid = false; + bool _skipCreateRootUser = false; + bool _isCreated = false; + + @override + void initState() { + super.initState(); + _createdUser = false; + _username = ""; + _password = ""; + _passwordVisible = false; + _isValid = false; + try { + _skipCreateRootUser = prefs.getBool("skipCreateRootUser") ?? false; + } catch (e) { + _log.warning("Failed to get skipCreateRootUser:", e); + _skipCreateRootUser = false; + } + _isCreated = false; + } + + Future _createRootUser(String username, String password) async { + if (!_createdUser) { + final re = await api.createUser(username, password); + if (re.ok) { + final id = re.unwrap(); + _createdUser = true; + _log.info("New user's id: $id"); + if (id != 0) { + _log.warning("The new user is not root user."); + } + } else if (re.status == 403 || re.status == 2) { + final e = re.unwrapErr(); + _log.warning("Failed to create root user:", e); + return false; + } else { + throw re.unwrapErr(); + } + } + return await login(username, password); + } + + static bool _checkIsValid(String username, String password) { + return (username.isNotEmpty && password.isNotEmpty); + } + + void _usernameChanged(String value) { + bool isValid = _checkIsValid(value, _password); + setState(() { + _username = value; + _isValid = isValid; + }); + } + + void _passwordChanged(String value) { + bool isValid = _checkIsValid(_username, value); + setState(() { + _password = value; + _isValid = isValid; + }); + } + + void _passwordVisibleChanged() { + setState(() { + _passwordVisible = !_passwordVisible; + }); + } + + @override + Widget build(BuildContext context) { + tryInitApi(context); + var actions = [buildThemeModeIcon(context)]; + if (_skipCreateRootUser) { + actions.add(buildMoreVertSettingsButon(context)); + } + return Scaffold( + appBar: AppBar( + leading: _skipCreateRootUser + ? IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + context.canPop() ? context.pop() : context.go("/"); + }, + ) + : null, + title: Text(AppLocalizations.of(context)!.createRootUser), + actions: actions, + ), + body: Container( + padding: MediaQuery.of(context).size.width > 810 + ? const EdgeInsets.symmetric(horizontal: 100) + : null, + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: TextFormField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.username, + ), + initialValue: _username, + onChanged: _usernameChanged, + )), + Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: TextFormField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.password, + suffixIcon: IconButton( + icon: Icon( + _passwordVisible + ? Icons.visibility + : Icons.visibility_off, + color: Theme.of(context).primaryColorDark, + ), + onPressed: _passwordVisibleChanged, + ), + ), + initialValue: _password, + onChanged: _passwordChanged, + obscureText: !_passwordVisible, + )), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () => { + prefs + .setBool("skipCreateRootUser", true) + .then((re) { + if (!re) { + _log.warning( + "Failed to set skipCreateRootUser."); + } else { + context.canPop() + ? context.pop() + : context.go("/"); + } + }).catchError((e) { + _log.warning( + "Failed to set skipCreateRootUser:", e); + }) + }, + child: Text(AppLocalizations.of(context)!.skip), + ), + ElevatedButton( + onPressed: !_isCreated && _isValid + ? () { + setState(() { + _isCreated = true; + }); + _createRootUser(_username, _password).then((re) { + if (re) { + auth.clear(); + context.canPop() + ? context.pop() + : context.go("/"); + } else { + if (!_createdUser) { + context.canPop() + ? context.pop() + : context.go("/"); + return; + } + final snackBar = SnackBar( + content: Text( + AppLocalizations.of(context)! + .incorrectUserPassword)); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + setState(() { + _isCreated = false; + }); + } + }).catchError((e) { + _log.severe( + "Failed to create root user:", e); + final isNetworkError = e is! (int, String); + final snackBar = SnackBar( + content: Text(isNetworkError + ? AppLocalizations.of(context)! + .networkError + : AppLocalizations.of(context)! + .internalError)); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + setState(() { + _isCreated = false; + }); + }); + } + : null, + child: Text(AppLocalizations.of(context)!.create), + ) + ], + ) + ], + )))); + } +} diff --git a/lib/globals.dart b/lib/globals.dart index 6c19a9b..24dbd76 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -113,18 +114,53 @@ final GlobalKey rootScaffoldMessengerKey = enum MoreVertSettings { setServerUrl, + createRootUser, } void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) { switch (value) { case MoreVertSettings.setServerUrl: - context.go("/set_server"); + context.push("/set_server"); + break; + case MoreVertSettings.createRootUser: + context.push("/create_root_user"); break; default: break; } } +List> buildMoreVertSettings( + BuildContext context) { + var list = >[]; + var path = GoRouterState.of(context).path; + if (const bool.fromEnvironment("skipBaseUrl") != true && + path != "/set_server") { + list.add(PopupMenuItem( + value: MoreVertSettings.setServerUrl, + child: Text(AppLocalizations.of(context)!.setServerUrl))); + } + if (auth.status != null && + auth.status!.noUser && + prefs.getBool("skipCreateRootUser") == true && + path != "/create_root_user") { + list.add(PopupMenuItem( + value: MoreVertSettings.createRootUser, + child: Text(AppLocalizations.of(context)!.createRootUser))); + } + return list; +} + +Widget buildMoreVertSettingsButon(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (MoreVertSettings value) { + onMoreVertSettingsSelected(context, value); + }, + itemBuilder: buildMoreVertSettings, + ); +} + ThemeMode themeModeNext(ThemeMode mode) { if (mode == ThemeMode.system) return ThemeMode.light; if (mode == ThemeMode.dark) return ThemeMode.system; diff --git a/lib/home.dart b/lib/home.dart index 3bf2771..fd725c2 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -15,19 +15,18 @@ class HomePage extends HookWidget { @override Widget build(BuildContext context) { - useEffect(() { - if (!tryInitApi(context)) return; - if (!auth.isAuthed) { - auth.checkAuth().then((re) { - if (!re) { - context.go(auth.status!.noUser ? "/create_root_user" : "/login"); - } - }).catchError((err) { - _log.log(Level.SEVERE, "Failed to check auth info:", err); - }); - } - return; - }, []); + tryInitApi(context); + if (!auth.isAuthed) { + auth.checkAuth().then((re) { + if (!re) { + if (auth.status!.noUser && + prefs.getBool("skipCreateRootUser") == true) return; + context.push(auth.status!.noUser ? "/create_root_user" : "/login"); + } + }).catchError((err) { + _log.log(Level.SEVERE, "Failed to check auth info:", err); + }); + } var mode = useState(MainApp.of(context).themeMode); return Scaffold( appBar: AppBar( @@ -44,21 +43,7 @@ class HomePage extends HookWidget { : mode.value == ThemeMode.dark ? Icons.dark_mode : Icons.light_mode)), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - onSelected: (MoreVertSettings value) { - onMoreVertSettingsSelected(context, value); - }, - itemBuilder: (BuildContext build) { - var list = >[]; - if (const bool.fromEnvironment("skipBaseUrl") != true) { - list.add(PopupMenuItem( - value: MoreVertSettings.setServerUrl, - child: Text(AppLocalizations.of(build)!.setServerUrl))); - } - return list; - }, - ), + buildMoreVertSettingsButon(context), ], ), body: const Center( diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 553c296..c784915 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -10,5 +10,8 @@ "incorrectUserPassword": "Incorrect username or password.", "networkError": "Network error.", "internalError": "Some internal error occurred. Please send the log file to the developer.", - "setServerUrl": "Server URL Settings" + "setServerUrl": "Server URL Settings", + "createRootUser": "Create Root User", + "skip": "Skip", + "create": "Create" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 974a014..06a9948 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -10,5 +10,8 @@ "incorrectUserPassword": "不正确的用户名或密码。", "networkError": "网络错误。", "internalError": "出现了内部错误,请将日志发送给开发者。", - "setServerUrl": "服务器地址设置" + "setServerUrl": "服务器地址设置", + "createRootUser": "创建根用户", + "skip": "跳过", + "create": "创建" } diff --git a/lib/login.dart b/lib/login.dart index 1b9eba6..9b3a07b 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -16,6 +16,21 @@ class LoginPage extends StatefulWidget { State createState() => _LoginPageState(); } +Future login(String username, String password) async { + String baseUrl = api.baseUrl!; + final u = Uri.parse(baseUrl); + _log.info("Secure level: ${u.scheme}"); + final re = await api.createToken( + username: username, + password: password, + setCookie: true, + httpOnly: true, + secure: u.scheme == 'https'); + if (re.ok) return true; + if (re.status == 4) return false; + throw re.unwrapErr(); +} + class _LoginPageState extends State with ThemeModeWidget { final _formKey = GlobalKey(); String _username = ""; @@ -61,20 +76,6 @@ class _LoginPageState extends State with ThemeModeWidget { }); } - static Future _login(String username, String password) async { - String baseUrl = api.baseUrl!; - final u = Uri.parse(baseUrl); - final re = await api.createToken( - username: username, - password: password, - setCookie: true, - httpOnly: true, - secure: u.scheme == 'https'); - if (re.ok) return true; - if (re.status == 4) return false; - throw re.unwrapErr(); - } - void _checkStatus(BuildContext build) { if (auth.isAuthed) { SchedulerBinding.instance.addPostFrameCallback((_) { @@ -153,9 +154,12 @@ class _LoginPageState extends State with ThemeModeWidget { setState(() { _isLogin = true; }); - _login(_username, _password).then((re) { + login(_username, _password).then((re) { if (re) { - context.go("/"); + auth.clear(); + context.canPop() + ? context.pop() + : context.go("/"); } else { final snackBar = SnackBar( content: Text( diff --git a/lib/main.dart b/lib/main.dart index e5fe8b5..efc7e4f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:window_manager/window_manager.dart'; +import 'create_root_user.dart'; import 'globals.dart'; import 'home.dart'; import 'login.dart'; @@ -17,7 +18,9 @@ final _router = GoRouter( routes: [ GoRoute( path: HomePage.routeName, - builder: (context, state) => const HomePage(), + // Add const will cause checkAuth not works when pop to home page. + // ignore: prefer_const_constructors + builder: (context, state) => HomePage(), ), GoRoute( path: SetServerPage.routeName, @@ -27,6 +30,10 @@ final _router = GoRouter( path: LoginPage.routeName, builder: (context, state) => const LoginPage(), ), + GoRoute( + path: CreateRootUserPage.routeName, + builder: (context, state) => const CreateRootUserPage(), + ) ], ); diff --git a/lib/set_server.dart b/lib/set_server.dart index dd161ab..f9f600b 100644 --- a/lib/set_server.dart +++ b/lib/set_server.dart @@ -72,6 +72,10 @@ class _SetServerPageState extends State with ThemeModeWidget { }); } final bool hasBaseUrl = prefs.getString('baseUrl') != null; + var actions = [ + buildThemeModeIcon(context), + ]; + if (hasBaseUrl) actions.add(buildMoreVertSettingsButon(context)); return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.setServerUrl), @@ -83,9 +87,7 @@ class _SetServerPageState extends State with ThemeModeWidget { }, ) : null, - actions: [ - buildThemeModeIcon(context), - ], + actions: actions, ), body: Container( padding: MediaQuery.of(context).size.width > 810