🐛 Fix refresh token
This commit is contained in:
		| @@ -35,5 +35,13 @@ | ||||
|   "loginEnterPassword": "Enter the code", | ||||
|   "loginSuccess": "Logged in as {}", | ||||
|   "authFactorPassword": "Password", | ||||
|   "authFactorEmail": "Email verification code" | ||||
|   "authFactorEmail": "Email verification code", | ||||
|   "accountIntroTitle": "Hello there!", | ||||
|   "accountIntroSubtitle": "Pick an option below to get started.", | ||||
|   "accountLogout": "Logout", | ||||
|   "accountLogoutSubtitle": "Log out of the current account.", | ||||
|   "accountLogoutConfirmTitle": "Are you sure you want to logout?", | ||||
|   "accountLogoutConfirm": "You will need to re-enter your account password, even if you have already done so. This is required to login again.", | ||||
|   "accountPublishers": "Your publishers", | ||||
|   "accountPublishersSubtitle": "Manage your publish identities." | ||||
| } | ||||
|   | ||||
| @@ -35,5 +35,13 @@ | ||||
|   "loginEnterPassword": "验证代码", | ||||
|   "loginSuccess": "登录为 {}", | ||||
|   "authFactorPassword": "密码", | ||||
|   "authFactorEmail": "电邮一次性验证码" | ||||
|   "authFactorEmail": "电邮一次性验证码", | ||||
|   "accountIntroTitle": "喜欢您来!", | ||||
|   "accountIntroSubtitle": "登陆以探索更广大的世界。", | ||||
|   "accountLogout": "退出登录", | ||||
|   "accountLogoutSubtitle": "注销当前账户的登陆状态。", | ||||
|   "accountLogoutConfirmTitle": "您确定要退出登录吗?", | ||||
|   "accountLogoutConfirm": "您需要重新输入账号密码,甚至可能需要多步验证来再次登陆。", | ||||
|   "accountPublishers": "你的发布者", | ||||
|   "accountPublishersSubtitle": "管理你的公共形象。" | ||||
| } | ||||
|   | ||||
| @@ -4,12 +4,11 @@ import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/types/attachment.dart'; | ||||
|  | ||||
| class SnAttachmentProvider { | ||||
|   late final SnNetworkProvider sn; | ||||
|  | ||||
|   late final SnNetworkProvider _sn; | ||||
|   final Map<String, SnAttachment> _cache = {}; | ||||
|  | ||||
|   SnAttachmentProvider(BuildContext context) { | ||||
|     sn = context.read<SnNetworkProvider>(); | ||||
|     _sn = context.read<SnNetworkProvider>(); | ||||
|   } | ||||
|  | ||||
|   Future<SnAttachment> getOne(String rid, {noCache = false}) async { | ||||
| @@ -17,7 +16,7 @@ class SnAttachmentProvider { | ||||
|       return _cache[rid]!; | ||||
|     } | ||||
|  | ||||
|     final resp = await sn.client.get('/cgi/uc/attachments/$rid/meta'); | ||||
|     final resp = await _sn.client.get('/cgi/uc/attachments/$rid/meta'); | ||||
|     final out = SnAttachment.fromJson(resp.data); | ||||
|     _cache[rid] = out; | ||||
|  | ||||
| @@ -33,7 +32,7 @@ class SnAttachmentProvider { | ||||
|       return rids.map((rid) => _cache[rid]!).toList(); | ||||
|     } | ||||
|  | ||||
|     final resp = await sn.client.get('/cgi/uc/attachments', queryParameters: { | ||||
|     final resp = await _sn.client.get('/cgi/uc/attachments', queryParameters: { | ||||
|       'take': pendingFetch.length, | ||||
|       'id': pendingFetch.join(','), | ||||
|     }); | ||||
|   | ||||
| @@ -67,7 +67,7 @@ class SnNetworkProvider { | ||||
|               final b64 = utf8.fuse(base64Url); | ||||
|               final payload = b64.decode(rawPayload); | ||||
|               final exp = jsonDecode(payload)['exp']; | ||||
|               if (exp >= DateTime.now().millisecondsSinceEpoch) { | ||||
|               if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) { | ||||
|                 log('Access token need refresh, doing it at ${DateTime.now()}'); | ||||
|                 atk = await refreshToken(); | ||||
|               } | ||||
| @@ -78,6 +78,8 @@ class SnNetworkProvider { | ||||
|                 log('Access token refresh failed...'); | ||||
|               } | ||||
|             } | ||||
|           } catch (err) { | ||||
|             log('Failed to authenticate user: $err'); | ||||
|           } finally { | ||||
|             handler.next(options); | ||||
|           } | ||||
| @@ -114,7 +116,12 @@ class SnNetworkProvider { | ||||
|     final rtk = await _storage.read(key: kRtkStoreKey); | ||||
|     if (rtk == null) return null; | ||||
|  | ||||
|     final resp = await client.post('/cgi/id/auth/token', data: { | ||||
|     final dio = Dio(); | ||||
|     dio.options.baseUrl = kUseLocalNetwork | ||||
|         ? 'http://localhost:8001' | ||||
|         : 'https://api.sn.solsynth.dev'; | ||||
|  | ||||
|     final resp = await dio.post('/cgi/id/auth/token', data: { | ||||
|       'grant_type': 'refresh_token', | ||||
|       'refresh_token': rtk, | ||||
|     }); | ||||
|   | ||||
| @@ -10,12 +10,11 @@ class UserProvider extends ChangeNotifier { | ||||
|   bool isAuthorized = false; | ||||
|   SnAccount? user; | ||||
|  | ||||
|   late final SnNetworkProvider sn; | ||||
|  | ||||
|   late final SnNetworkProvider _sn; | ||||
|   late final FlutterSecureStorage _storage = FlutterSecureStorage(); | ||||
|  | ||||
|   UserProvider(BuildContext context) { | ||||
|     sn = context.read<SnNetworkProvider>(); | ||||
|     _sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     _storage.read(key: kAtkStoreKey).then((value) { | ||||
|       isAuthorized = value != null; | ||||
| @@ -31,7 +30,7 @@ class UserProvider extends ChangeNotifier { | ||||
|   Future<SnAccount?> refreshUser() async { | ||||
|     if (!isAuthorized) return null; | ||||
|  | ||||
|     final resp = await sn.client.get('/cgi/id/users/me'); | ||||
|     final resp = await _sn.client.get('/cgi/id/users/me'); | ||||
|     final out = SnAccount.fromJson(resp.data); | ||||
|  | ||||
|     user = out; | ||||
| @@ -39,4 +38,11 @@ class UserProvider extends ChangeNotifier { | ||||
|  | ||||
|     return out; | ||||
|   } | ||||
|  | ||||
|   void logoutUser() async { | ||||
|     _sn.clearTokenPair(); | ||||
|     isAuthorized = false; | ||||
|     user = null; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,44 +1,153 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/widgets/account/account_image.dart'; | ||||
| import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
|  | ||||
| class AccountScreen extends StatefulWidget { | ||||
| class AccountScreen extends StatelessWidget { | ||||
|   const AccountScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   State<AccountScreen> createState() => _AccountScreenState(); | ||||
| } | ||||
|  | ||||
| class _AccountScreenState extends State<AccountScreen> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.watch<UserProvider>(); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       appBar: AppBar( | ||||
|         title: Text("screenAccount").tr(), | ||||
|       ), | ||||
|       body: ListView( | ||||
|         children: [ | ||||
|           ListTile( | ||||
|             title: Text('screenAuthLogin').tr(), | ||||
|             subtitle: Text('screenAuthLoginSubtitle').tr(), | ||||
|             contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|             trailing: const Icon(Icons.chevron_right), | ||||
|             onTap: () { | ||||
|               GoRouter.of(context).pushNamed('authLogin'); | ||||
|             }, | ||||
|           ), | ||||
|           ListTile( | ||||
|             title: Text('screenAuthRegister').tr(), | ||||
|             subtitle: Text('screenAuthRegisterSubtitle').tr(), | ||||
|             contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|             trailing: const Icon(Icons.chevron_right), | ||||
|             onTap: () { | ||||
|               GoRouter.of(context).pushNamed('authRegister'); | ||||
|             }, | ||||
|           ) | ||||
|         ], | ||||
|       body: SingleChildScrollView( | ||||
|         child: ua.isAuthorized | ||||
|             ? _AuthorizedAccountScreen() | ||||
|             : _UnauthorizedAccountScreen(), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _AuthorizedAccountScreen extends StatelessWidget { | ||||
|   const _AuthorizedAccountScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.watch<UserProvider>(); | ||||
|  | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Card( | ||||
|           child: Builder(builder: (context) { | ||||
|             if (ua.user == null) { | ||||
|               return SizedBox( | ||||
|                 width: double.infinity, | ||||
|                 height: 120, | ||||
|                 child: CircularProgressIndicator().center(), | ||||
|               ); | ||||
|             } | ||||
|  | ||||
|             return SizedBox( | ||||
|               width: double.infinity, | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   AccountImage(content: ua.user!.avatar, radius: 28), | ||||
|                   const Gap(8), | ||||
|                   Row( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|                     textBaseline: TextBaseline.alphabetic, | ||||
|                     children: [ | ||||
|                       Text(ua.user!.nick) | ||||
|                           .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||
|                       const Gap(4), | ||||
|                       Text('@${ua.user!.name}') | ||||
|                           .textStyle(Theme.of(context).textTheme.bodySmall!), | ||||
|                     ], | ||||
|                   ), | ||||
|                   Text(ua.user!.description) | ||||
|                       .textStyle(Theme.of(context).textTheme.bodyMedium!), | ||||
|                 ], | ||||
|               ), | ||||
|             ); | ||||
|           }).padding(all: 20), | ||||
|         ).padding(horizontal: 8, top: 16, bottom: 4), | ||||
|         ListTile( | ||||
|           title: Text('accountPublishers').tr(), | ||||
|           subtitle: Text('accountPublishersSubtitle').tr(), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           leading: const Icon(Symbols.face), | ||||
|           trailing: const Icon(Icons.chevron_right), | ||||
|           onTap: () {}, | ||||
|         ), | ||||
|         ListTile( | ||||
|           title: Text('accountLogout').tr(), | ||||
|           subtitle: Text('accountLogoutSubtitle').tr(), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           leading: const Icon(Symbols.logout), | ||||
|           trailing: const Icon(Icons.chevron_right), | ||||
|           onTap: () { | ||||
|             context | ||||
|                 .showConfirmDialog( | ||||
|               'accountLogoutConfirmTitle'.tr(), | ||||
|               'accountLogoutConfirm'.tr(), | ||||
|             ) | ||||
|                 .then((value) { | ||||
|               if (value) ua.logoutUser(); | ||||
|             }); | ||||
|           }, | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _UnauthorizedAccountScreen extends StatelessWidget { | ||||
|   const _UnauthorizedAccountScreen({super.key}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Column( | ||||
|       children: [ | ||||
|         Card( | ||||
|           child: SizedBox( | ||||
|             width: double.infinity, | ||||
|             child: Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Icon(Symbols.waving_hand, size: 32), | ||||
|                 const Gap(8), | ||||
|                 Text('accountIntroTitle') | ||||
|                     .tr() | ||||
|                     .textStyle(Theme.of(context).textTheme.titleLarge!), | ||||
|                 Text('accountIntroSubtitle').tr(), | ||||
|               ], | ||||
|             ).padding(all: 20), | ||||
|           ), | ||||
|         ).padding(horizontal: 8, top: 16, bottom: 4), | ||||
|         ListTile( | ||||
|           title: Text('screenAuthLogin').tr(), | ||||
|           subtitle: Text('screenAuthLoginSubtitle').tr(), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           leading: const Icon(Symbols.login), | ||||
|           trailing: const Icon(Icons.chevron_right), | ||||
|           onTap: () { | ||||
|             GoRouter.of(context).pushNamed('authLogin'); | ||||
|           }, | ||||
|         ), | ||||
|         ListTile( | ||||
|           title: Text('screenAuthRegister').tr(), | ||||
|           subtitle: Text('screenAuthRegisterSubtitle').tr(), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           leading: const Icon(Symbols.person_add), | ||||
|           trailing: const Icon(Icons.chevron_right), | ||||
|           onTap: () { | ||||
|             GoRouter.of(context).pushNamed('authRegister'); | ||||
|           }, | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user