🐛 Fix refresh token
This commit is contained in:
parent
4d12d243b3
commit
a629f5e12c
@ -35,5 +35,13 @@
|
|||||||
"loginEnterPassword": "Enter the code",
|
"loginEnterPassword": "Enter the code",
|
||||||
"loginSuccess": "Logged in as {}",
|
"loginSuccess": "Logged in as {}",
|
||||||
"authFactorPassword": "Password",
|
"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": "验证代码",
|
"loginEnterPassword": "验证代码",
|
||||||
"loginSuccess": "登录为 {}",
|
"loginSuccess": "登录为 {}",
|
||||||
"authFactorPassword": "密码",
|
"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';
|
import 'package:surface/types/attachment.dart';
|
||||||
|
|
||||||
class SnAttachmentProvider {
|
class SnAttachmentProvider {
|
||||||
late final SnNetworkProvider sn;
|
late final SnNetworkProvider _sn;
|
||||||
|
|
||||||
final Map<String, SnAttachment> _cache = {};
|
final Map<String, SnAttachment> _cache = {};
|
||||||
|
|
||||||
SnAttachmentProvider(BuildContext context) {
|
SnAttachmentProvider(BuildContext context) {
|
||||||
sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SnAttachment> getOne(String rid, {noCache = false}) async {
|
Future<SnAttachment> getOne(String rid, {noCache = false}) async {
|
||||||
@ -17,7 +16,7 @@ class SnAttachmentProvider {
|
|||||||
return _cache[rid]!;
|
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);
|
final out = SnAttachment.fromJson(resp.data);
|
||||||
_cache[rid] = out;
|
_cache[rid] = out;
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ class SnAttachmentProvider {
|
|||||||
return rids.map((rid) => _cache[rid]!).toList();
|
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,
|
'take': pendingFetch.length,
|
||||||
'id': pendingFetch.join(','),
|
'id': pendingFetch.join(','),
|
||||||
});
|
});
|
||||||
|
@ -67,7 +67,7 @@ class SnNetworkProvider {
|
|||||||
final b64 = utf8.fuse(base64Url);
|
final b64 = utf8.fuse(base64Url);
|
||||||
final payload = b64.decode(rawPayload);
|
final payload = b64.decode(rawPayload);
|
||||||
final exp = jsonDecode(payload)['exp'];
|
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()}');
|
log('Access token need refresh, doing it at ${DateTime.now()}');
|
||||||
atk = await refreshToken();
|
atk = await refreshToken();
|
||||||
}
|
}
|
||||||
@ -78,6 +78,8 @@ class SnNetworkProvider {
|
|||||||
log('Access token refresh failed...');
|
log('Access token refresh failed...');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log('Failed to authenticate user: $err');
|
||||||
} finally {
|
} finally {
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
@ -114,7 +116,12 @@ class SnNetworkProvider {
|
|||||||
final rtk = await _storage.read(key: kRtkStoreKey);
|
final rtk = await _storage.read(key: kRtkStoreKey);
|
||||||
if (rtk == null) return null;
|
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',
|
'grant_type': 'refresh_token',
|
||||||
'refresh_token': rtk,
|
'refresh_token': rtk,
|
||||||
});
|
});
|
||||||
|
@ -10,12 +10,11 @@ class UserProvider extends ChangeNotifier {
|
|||||||
bool isAuthorized = false;
|
bool isAuthorized = false;
|
||||||
SnAccount? user;
|
SnAccount? user;
|
||||||
|
|
||||||
late final SnNetworkProvider sn;
|
late final SnNetworkProvider _sn;
|
||||||
|
|
||||||
late final FlutterSecureStorage _storage = FlutterSecureStorage();
|
late final FlutterSecureStorage _storage = FlutterSecureStorage();
|
||||||
|
|
||||||
UserProvider(BuildContext context) {
|
UserProvider(BuildContext context) {
|
||||||
sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
_storage.read(key: kAtkStoreKey).then((value) {
|
_storage.read(key: kAtkStoreKey).then((value) {
|
||||||
isAuthorized = value != null;
|
isAuthorized = value != null;
|
||||||
@ -31,7 +30,7 @@ class UserProvider extends ChangeNotifier {
|
|||||||
Future<SnAccount?> refreshUser() async {
|
Future<SnAccount?> refreshUser() async {
|
||||||
if (!isAuthorized) return null;
|
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);
|
final out = SnAccount.fromJson(resp.data);
|
||||||
|
|
||||||
user = out;
|
user = out;
|
||||||
@ -39,4 +38,11 @@ class UserProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logoutUser() async {
|
||||||
|
_sn.clearTokenPair();
|
||||||
|
isAuthorized = false;
|
||||||
|
user = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,137 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.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';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class AccountScreen extends StatefulWidget {
|
class AccountScreen extends StatelessWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<AccountScreen> createState() => _AccountScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AccountScreenState extends State<AccountScreen> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ua = context.watch<UserProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("screenAccount").tr(),
|
title: Text("screenAccount").tr(),
|
||||||
),
|
),
|
||||||
body: ListView(
|
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: [
|
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(
|
ListTile(
|
||||||
title: Text('screenAuthLogin').tr(),
|
title: Text('screenAuthLogin').tr(),
|
||||||
subtitle: Text('screenAuthLoginSubtitle').tr(),
|
subtitle: Text('screenAuthLoginSubtitle').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.login),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed('authLogin');
|
GoRouter.of(context).pushNamed('authLogin');
|
||||||
@ -32,13 +141,13 @@ class _AccountScreenState extends State<AccountScreen> {
|
|||||||
title: Text('screenAuthRegister').tr(),
|
title: Text('screenAuthRegister').tr(),
|
||||||
subtitle: Text('screenAuthRegisterSubtitle').tr(),
|
subtitle: Text('screenAuthRegisterSubtitle').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.person_add),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed('authRegister');
|
GoRouter.of(context).pushNamed('authRegister');
|
||||||
},
|
},
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user