Optimized refresh credentials

This commit is contained in:
LittleSheep 2024-07-07 11:56:25 +08:00
parent 22ee817676
commit 75c753ef63
5 changed files with 47 additions and 28 deletions

View File

@ -56,10 +56,10 @@ class AccountProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
await auth.ensureCredentials(); await auth.ensureCredentials();
if (globalCredentials == null) await auth.loadCredentials(); if (auth.credentials == null) await auth.loadCredentials();
final uri = Uri.parse( final uri = Uri.parse(
'${ServiceFinder.services['passport']}/api/ws?tk=${globalCredentials!.accessToken}' '${ServiceFinder.services['passport']}/api/ws?tk=${auth.credentials!.accessToken}'
.replaceFirst('http', 'ws'), .replaceFirst('http', 'ws'),
); );

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.dart'; import 'package:get/get_connect/http/src/request/request.dart';
import 'package:mutex/mutex.dart';
import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/providers/account.dart'; import 'package:solian/providers/account.dart';
import 'package:solian/providers/chat.dart'; import 'package:solian/providers/chat.dart';
@ -39,8 +40,6 @@ class TokenSet {
bool get isExpired => expiredAt?.isBefore(DateTime.now()) ?? true; bool get isExpired => expiredAt?.isBefore(DateTime.now()) ?? true;
} }
TokenSet? globalCredentials;
class RiskyAuthenticateException implements Exception { class RiskyAuthenticateException implements Exception {
final int ticketId; final int ticketId;
@ -56,6 +55,9 @@ class AuthProvider extends GetConnect {
static const storage = FlutterSecureStorage(); static const storage = FlutterSecureStorage();
TokenSet? credentials;
Mutex credentialsRefreshMutex = Mutex();
@override @override
void onInit() { void onInit() {
httpClient.baseUrl = ServiceFinder.services['passport']; httpClient.baseUrl = ServiceFinder.services['passport'];
@ -63,28 +65,36 @@ class AuthProvider extends GetConnect {
} }
Future<void> refreshCredentials() async { Future<void> refreshCredentials() async {
try {
credentialsRefreshMutex.acquire();
if (!credentials!.isExpired) return;
final resp = await post('/api/auth/token', { final resp = await post('/api/auth/token', {
'refresh_token': globalCredentials!.refreshToken, 'refresh_token': credentials!.refreshToken,
'grant_type': 'refresh_token', 'grant_type': 'refresh_token',
}); });
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception(resp.bodyString); throw Exception(resp.bodyString);
} }
globalCredentials = TokenSet( credentials = TokenSet(
accessToken: resp.body['access_token'], accessToken: resp.body['access_token'],
refreshToken: resp.body['refresh_token'], refreshToken: resp.body['refresh_token'],
expiredAt: DateTime.now().add(const Duration(minutes: 3)), expiredAt: DateTime.now().add(const Duration(minutes: 3)),
); );
storage.write( storage.write(
key: 'auth_credentials', key: 'auth_credentials',
value: jsonEncode(globalCredentials!.toJson()), value: jsonEncode(credentials!.toJson()),
); );
} catch (_) {
rethrow;
} finally {
credentialsRefreshMutex.release();
}
} }
Future<Request<T?>> requestAuthenticator<T>(Request<T?> request) async { Future<Request<T?>> requestAuthenticator<T>(Request<T?> request) async {
try { try {
await ensureCredentials(); await ensureCredentials();
request.headers['Authorization'] = 'Bearer ${globalCredentials!.accessToken}'; request.headers['Authorization'] = 'Bearer ${credentials!.accessToken}';
} catch (_) {} } catch (_) {}
return request; return request;
@ -108,9 +118,9 @@ class AuthProvider extends GetConnect {
Future<void> ensureCredentials() async { Future<void> ensureCredentials() async {
if (!await isAuthorized) throw Exception('unauthorized'); if (!await isAuthorized) throw Exception('unauthorized');
if (globalCredentials == null) await loadCredentials(); if (credentials == null) await loadCredentials();
if (globalCredentials!.isExpired) { if (credentials!.isExpired) {
await refreshCredentials(); await refreshCredentials();
log('Refreshed credentials at ${DateTime.now()}'); log('Refreshed credentials at ${DateTime.now()}');
} }
@ -119,7 +129,7 @@ class AuthProvider extends GetConnect {
Future<void> loadCredentials() async { Future<void> loadCredentials() async {
if (await isAuthorized) { if (await isAuthorized) {
final content = await storage.read(key: 'auth_credentials'); final content = await storage.read(key: 'auth_credentials');
globalCredentials = TokenSet.fromJson(jsonDecode(content!)); credentials = TokenSet.fromJson(jsonDecode(content!));
} }
} }
@ -152,7 +162,7 @@ class AuthProvider extends GetConnect {
throw Exception(tokenResp.bodyString); throw Exception(tokenResp.bodyString);
} }
globalCredentials = TokenSet( credentials = TokenSet(
accessToken: tokenResp.body['access_token'], accessToken: tokenResp.body['access_token'],
refreshToken: tokenResp.body['refresh_token'], refreshToken: tokenResp.body['refresh_token'],
expiredAt: DateTime.now().add(const Duration(minutes: 3)), expiredAt: DateTime.now().add(const Duration(minutes: 3)),
@ -160,14 +170,14 @@ class AuthProvider extends GetConnect {
storage.write( storage.write(
key: 'auth_credentials', key: 'auth_credentials',
value: jsonEncode(globalCredentials!.toJson()), value: jsonEncode(credentials!.toJson()),
); );
Get.find<AccountProvider>().connect(); Get.find<AccountProvider>().connect();
Get.find<AccountProvider>().notifyPrefetch(); Get.find<AccountProvider>().notifyPrefetch();
Get.find<ChatProvider>().connect(); Get.find<ChatProvider>().connect();
return globalCredentials!; return credentials!;
} }
void signout() { void signout() {

View File

@ -26,10 +26,10 @@ class ChatProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
await auth.ensureCredentials(); await auth.ensureCredentials();
if (globalCredentials == null) await auth.loadCredentials(); if (auth.credentials == null) await auth.loadCredentials();
final uri = Uri.parse( final uri = Uri.parse(
'${ServiceFinder.services['messaging']}/api/ws?tk=${globalCredentials!.accessToken}' '${ServiceFinder.services['messaging']}/api/ws?tk=${auth.credentials!.accessToken}'
.replaceFirst('http', 'ws'), .replaceFirst('http', 'ws'),
); );

View File

@ -1008,6 +1008,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
mutex:
dependency: "direct main"
description:
name: mutex
sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
nm: nm:
dependency: transitive dependency: transitive
description: description:

View File

@ -53,6 +53,7 @@ dependencies:
media_kit_video: ^1.2.4 media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4 media_kit_libs_video: ^1.0.4
textfield_tags: ^3.0.1 textfield_tags: ^3.0.1
mutex: ^3.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: