🐛 Fix bugs and optimize your auth experience
This commit is contained in:
@ -56,10 +56,10 @@ class AccountProvider extends GetxController {
|
||||
final AuthProvider auth = Get.find();
|
||||
await auth.ensureCredentials();
|
||||
|
||||
if (auth.credentials == null) await auth.loadCredentials();
|
||||
if (globalCredentials == null) await auth.loadCredentials();
|
||||
|
||||
final uri = Uri.parse(
|
||||
'${ServiceFinder.services['passport']}/api/ws?tk=${auth.credentials!.accessToken}'
|
||||
'${ServiceFinder.services['passport']}/api/ws?tk=${globalCredentials!.accessToken}'
|
||||
.replaceFirst('http', 'ws'),
|
||||
);
|
||||
|
||||
|
@ -10,7 +10,42 @@ import 'package:solian/controllers/chat_events_controller.dart';
|
||||
import 'package:solian/providers/account.dart';
|
||||
import 'package:solian/providers/chat.dart';
|
||||
import 'package:solian/services.dart';
|
||||
import 'package:oauth2/oauth2.dart' as oauth2;
|
||||
|
||||
class TokenSet {
|
||||
final String accessToken;
|
||||
final String refreshToken;
|
||||
final DateTime? expiredAt;
|
||||
|
||||
TokenSet({
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
this.expiredAt,
|
||||
});
|
||||
|
||||
factory TokenSet.fromJson(Map<String, dynamic> json) => TokenSet(
|
||||
accessToken: json['access_token'],
|
||||
refreshToken: json['refresh_token'],
|
||||
expiredAt: json['expired_at'] != null
|
||||
? DateTime.parse(json['expired_at'])
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'access_token': accessToken,
|
||||
'refresh_token': refreshToken,
|
||||
'expired_at': expiredAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
bool get isExpired => expiredAt?.isBefore(DateTime.now()) ?? true;
|
||||
}
|
||||
|
||||
TokenSet? globalCredentials;
|
||||
|
||||
class RiskyAuthenticateException implements Exception {
|
||||
final int ticketId;
|
||||
|
||||
RiskyAuthenticateException(this.ticketId);
|
||||
}
|
||||
|
||||
class AuthProvider extends GetConnect {
|
||||
final tokenEndpoint =
|
||||
@ -27,33 +62,29 @@ class AuthProvider extends GetConnect {
|
||||
loadCredentials();
|
||||
}
|
||||
|
||||
oauth2.Credentials? credentials;
|
||||
|
||||
Future<void> refreshCredentials() async {
|
||||
final resp = await post('/api/auth/token', {
|
||||
'refresh_token': credentials!.refreshToken,
|
||||
'refresh_token': globalCredentials!.refreshToken,
|
||||
'grant_type': 'refresh_token',
|
||||
});
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception(resp.bodyString);
|
||||
}
|
||||
credentials = oauth2.Credentials(
|
||||
resp.body['access_token'],
|
||||
globalCredentials = TokenSet(
|
||||
accessToken: resp.body['access_token'],
|
||||
refreshToken: resp.body['refresh_token'],
|
||||
idToken: resp.body['access_token'],
|
||||
tokenEndpoint: tokenEndpoint,
|
||||
expiration: DateTime.now().add(const Duration(minutes: 3)),
|
||||
expiredAt: DateTime.now().add(const Duration(minutes: 3)),
|
||||
);
|
||||
storage.write(
|
||||
key: 'auth_credentials',
|
||||
value: jsonEncode(credentials!.toJson()),
|
||||
value: jsonEncode(globalCredentials!.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Request<T?>> requestAuthenticator<T>(Request<T?> request) async {
|
||||
try {
|
||||
await ensureCredentials();
|
||||
request.headers['Authorization'] = 'Bearer ${credentials!.accessToken}';
|
||||
request.headers['Authorization'] = 'Bearer ${globalCredentials!.accessToken}';
|
||||
} catch (_) {}
|
||||
|
||||
return request;
|
||||
@ -66,7 +97,8 @@ class AuthProvider extends GetConnect {
|
||||
final client = GetConnect(
|
||||
maxAuthRetries: 3,
|
||||
timeout: timeout,
|
||||
allowAutoSignedCert: true,
|
||||
userAgent: 'Solian/1.1',
|
||||
sendUserAgent: true,
|
||||
);
|
||||
client.httpClient.addAuthenticator(requestAuthenticator);
|
||||
client.httpClient.baseUrl = ServiceFinder.services[service];
|
||||
@ -76,9 +108,9 @@ class AuthProvider extends GetConnect {
|
||||
|
||||
Future<void> ensureCredentials() async {
|
||||
if (!await isAuthorized) throw Exception('unauthorized');
|
||||
if (credentials == null) await loadCredentials();
|
||||
if (globalCredentials == null) await loadCredentials();
|
||||
|
||||
if (credentials!.isExpired) {
|
||||
if (globalCredentials!.isExpired) {
|
||||
await refreshCredentials();
|
||||
log('Refreshed credentials at ${DateTime.now()}');
|
||||
}
|
||||
@ -87,45 +119,55 @@ class AuthProvider extends GetConnect {
|
||||
Future<void> loadCredentials() async {
|
||||
if (await isAuthorized) {
|
||||
final content = await storage.read(key: 'auth_credentials');
|
||||
credentials = oauth2.Credentials.fromJson(jsonDecode(content!));
|
||||
globalCredentials = TokenSet.fromJson(jsonDecode(content!));
|
||||
}
|
||||
}
|
||||
|
||||
Future<oauth2.Credentials> signin(
|
||||
Future<TokenSet> signin(
|
||||
BuildContext context,
|
||||
String username,
|
||||
String password,
|
||||
) async {
|
||||
_cachedUserProfileResponse = null;
|
||||
|
||||
final resp = await oauth2.resourceOwnerPasswordGrant(
|
||||
tokenEndpoint,
|
||||
username,
|
||||
password,
|
||||
identifier: clientId,
|
||||
secret: clientSecret,
|
||||
scopes: ['*'],
|
||||
basicAuth: false,
|
||||
);
|
||||
final client = ServiceFinder.configureClient('passport');
|
||||
|
||||
credentials = oauth2.Credentials(
|
||||
resp.credentials.accessToken,
|
||||
refreshToken: resp.credentials.refreshToken!,
|
||||
idToken: resp.credentials.accessToken,
|
||||
tokenEndpoint: tokenEndpoint,
|
||||
expiration: DateTime.now().add(const Duration(minutes: 3)),
|
||||
// Create ticket
|
||||
final resp = await client.post('/api/auth', {
|
||||
'username': username,
|
||||
'password': password,
|
||||
});
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception(resp.body);
|
||||
} else if (resp.body['is_finished'] == false) {
|
||||
throw RiskyAuthenticateException(resp.body['ticket']['id']);
|
||||
}
|
||||
|
||||
// Assign token
|
||||
final tokenResp = await post('/api/auth/token', {
|
||||
'code': resp.body['ticket']['grant_token'],
|
||||
'grant_type': 'grant_token',
|
||||
});
|
||||
if (tokenResp.statusCode != 200) {
|
||||
throw Exception(tokenResp.bodyString);
|
||||
}
|
||||
|
||||
globalCredentials = TokenSet(
|
||||
accessToken: tokenResp.body['access_token'],
|
||||
refreshToken: tokenResp.body['refresh_token'],
|
||||
expiredAt: DateTime.now().add(const Duration(minutes: 3)),
|
||||
);
|
||||
|
||||
storage.write(
|
||||
key: 'auth_credentials',
|
||||
value: jsonEncode(credentials!.toJson()),
|
||||
value: jsonEncode(globalCredentials!.toJson()),
|
||||
);
|
||||
|
||||
Get.find<AccountProvider>().connect();
|
||||
Get.find<AccountProvider>().notifyPrefetch();
|
||||
Get.find<ChatProvider>().connect();
|
||||
|
||||
return credentials!;
|
||||
return globalCredentials!;
|
||||
}
|
||||
|
||||
void signout() {
|
||||
|
@ -26,8 +26,10 @@ class ChatProvider extends GetxController {
|
||||
final AuthProvider auth = Get.find();
|
||||
await auth.ensureCredentials();
|
||||
|
||||
if (globalCredentials == null) await auth.loadCredentials();
|
||||
|
||||
final uri = Uri.parse(
|
||||
'${ServiceFinder.services['messaging']}/api/ws?tk=${auth.credentials!.accessToken}'
|
||||
'${ServiceFinder.services['messaging']}/api/ws?tk=${globalCredentials!.accessToken}'
|
||||
.replaceFirst('http', 'ws'),
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user