Refactor and streamline auth store with comments
This commit is contained in:
parent
9b02b2b4c4
commit
14b7aa9662
2 changed files with 139 additions and 86 deletions
|
|
@ -1,114 +1,86 @@
|
|||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:frosty/constants.dart';
|
||||
import 'package:frosty/models/user.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter_web_auth/flutter_web_auth.dart';
|
||||
|
||||
part 'auth_store.g.dart';
|
||||
|
||||
class AuthStore = AuthBase with _$AuthStore;
|
||||
|
||||
abstract class AuthBase with Store {
|
||||
static String? _token;
|
||||
static String? get token => _token;
|
||||
|
||||
static Map<String, String>? authHeaders;
|
||||
class AuthStore = _AuthBase with _$AuthStore;
|
||||
|
||||
abstract class _AuthBase with Store {
|
||||
// Secure storage to store tokens
|
||||
final _storage = const FlutterSecureStorage();
|
||||
final _secret = secret;
|
||||
final _clientId = clientId;
|
||||
|
||||
// The current token
|
||||
@observable
|
||||
String? _token;
|
||||
String? get token => _token;
|
||||
|
||||
// Whether the user is logged in or not
|
||||
@observable
|
||||
var _isLoggedIn = false;
|
||||
|
||||
bool get isLoggedIn => _isLoggedIn;
|
||||
|
||||
// Whether the token is valid or not
|
||||
@observable
|
||||
var _tokenIsValid = false;
|
||||
|
||||
UserTwitch? user;
|
||||
// The current user's info
|
||||
@observable
|
||||
UserTwitch? _user;
|
||||
UserTwitch? get user => _user;
|
||||
|
||||
// Authentication headers for Twitch API requests
|
||||
@computed
|
||||
Map<String, String> get headersTwitch => {'Authorization': 'Bearer $_token', 'Client-Id': clientId};
|
||||
|
||||
/// Initialize by retrieving a token if it does not already exist.
|
||||
@action
|
||||
Future<void> init() async {
|
||||
// Read and set the currently stored user token, if any.
|
||||
_token = await _storage.read(key: 'USER_TOKEN');
|
||||
|
||||
// If the token does not exist, get the default token.
|
||||
// Otherwise, log in and get the user info.
|
||||
if (token != null) {
|
||||
_isLoggedIn = true;
|
||||
authHeaders = {'Authorization': 'Bearer $token', 'Client-Id': _clientId};
|
||||
await _getUserInfo();
|
||||
if (_token == null) {
|
||||
// Retrieve the currently stored default token if it exists
|
||||
_token = await _storage.read(key: 'DEFAULT_TOKEN');
|
||||
// If the token does not exist, get a new token and store it
|
||||
if (_token == null) {
|
||||
_token = await Twitch.getDefaultToken();
|
||||
await _storage.write(key: 'DEFAULT_TOKEN', value: _token);
|
||||
}
|
||||
} else {
|
||||
await _getDefaultToken();
|
||||
_isLoggedIn = true;
|
||||
_user = await Twitch.getUserInfo(headers: headersTwitch);
|
||||
}
|
||||
|
||||
// Set the auth headers for future requests and validate the token.
|
||||
authHeaders = {'Authorization': 'Bearer $token', 'Client-Id': _clientId};
|
||||
await _validateToken();
|
||||
// Validate the token
|
||||
_tokenIsValid = await Twitch.validateToken(token: _token!);
|
||||
|
||||
debugPrint('Token is valid: $_tokenIsValid');
|
||||
|
||||
debugPrint('Created auth provider');
|
||||
}
|
||||
|
||||
/// Returns a token for an anonymous user.
|
||||
Future<void> _getDefaultToken() async {
|
||||
debugPrint('Getting default token...');
|
||||
_token = await _storage.read(key: 'DEFAULT_TOKEN');
|
||||
|
||||
if (token != null) return;
|
||||
|
||||
final url = Uri(
|
||||
scheme: 'https',
|
||||
host: 'id.twitch.tv',
|
||||
path: '/oauth2/token',
|
||||
queryParameters: {
|
||||
'client_id': _clientId,
|
||||
'client_secret': _secret,
|
||||
'grant_type': 'client_credentials',
|
||||
},
|
||||
);
|
||||
|
||||
final response = await http.post(url);
|
||||
final defaultToken = jsonDecode(response.body)['access_token'];
|
||||
|
||||
await _storage.write(key: 'DEFAULT_TOKEN', value: defaultToken);
|
||||
|
||||
_token = defaultToken;
|
||||
}
|
||||
|
||||
Future<void> _validateToken() async {
|
||||
debugPrint('Validating token...');
|
||||
final response = await http.get(Uri.parse('https://id.twitch.tv/oauth2/validate'), headers: {'Authorization': 'Bearer $token'});
|
||||
if (response.statusCode == 200) {
|
||||
debugPrint('Token validated!');
|
||||
_tokenIsValid = true;
|
||||
// If the token is invalid, logout
|
||||
if (!_tokenIsValid) {
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('Token invalidated :(');
|
||||
_tokenIsValid = false;
|
||||
|
||||
// notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _getUserInfo() async {
|
||||
final response = await http.get(Uri.parse('https://api.twitch.tv/helix/users'), headers: authHeaders);
|
||||
final userData = jsonDecode(response.body)['data'] as List;
|
||||
|
||||
user = UserTwitch.fromJson(userData.first);
|
||||
debugPrint('Token is valid: $_tokenIsValid');
|
||||
debugPrint('Created auth provider');
|
||||
}
|
||||
|
||||
/// Initiates the OAuth sign-in process and updates fields accordingly upon successful login
|
||||
@action
|
||||
Future<void> login() async {
|
||||
// Create the OAuth sign-in URI
|
||||
final loginUrl = Uri(
|
||||
scheme: 'https',
|
||||
host: 'id.twitch.tv',
|
||||
path: '/oauth2/authorize',
|
||||
queryParameters: {
|
||||
'client_id': _clientId,
|
||||
'client_id': clientId,
|
||||
'redirect_uri': 'auth://',
|
||||
'response_type': 'token',
|
||||
'scope': 'chat:read chat:edit user:read:follows',
|
||||
|
|
@ -116,30 +88,37 @@ abstract class AuthBase with Store {
|
|||
);
|
||||
|
||||
try {
|
||||
// Retrieve the OAuth redirect URI
|
||||
final result = await FlutterWebAuth.authenticate(url: loginUrl.toString(), callbackUrlScheme: 'auth', preferEphemeral: true);
|
||||
|
||||
// Parse the user token from the redirect URI fragment
|
||||
final fragment = Uri.parse(result).fragment;
|
||||
|
||||
_token = fragment.substring(fragment.indexOf('=') + 1, fragment.indexOf('&'));
|
||||
await _storage.write(key: 'USER_TOKEN', value: token);
|
||||
|
||||
// Store the user token
|
||||
await _storage.write(key: 'USER_TOKEN', value: _token);
|
||||
|
||||
// Retrieve the user info and set it
|
||||
_user = await Twitch.getUserInfo(headers: headersTwitch);
|
||||
|
||||
// Set the login status to logged in
|
||||
_isLoggedIn = true;
|
||||
authHeaders = {'Authorization': 'Bearer $token', 'Client-Id': _clientId};
|
||||
|
||||
await _getUserInfo();
|
||||
|
||||
// notifyListeners();
|
||||
} catch (error) {
|
||||
debugPrint('Login failed due to $error');
|
||||
}
|
||||
}
|
||||
|
||||
void logout() async {
|
||||
await _getDefaultToken();
|
||||
_isLoggedIn = false;
|
||||
// Logs out the current user and updates fields accordingly
|
||||
@action
|
||||
Future<void> logout() async {
|
||||
// If the default token already exists, set it. Otherwise, get a new default token.
|
||||
_token = await _storage.read(key: 'DEFAULT_TOKEN') ?? await Twitch.getDefaultToken();
|
||||
|
||||
await _storage.delete(key: 'USER_TOKEN');
|
||||
debugPrint('Succesfully logged out');
|
||||
// notifyListeners();
|
||||
// Clear the user info
|
||||
_user = null;
|
||||
|
||||
// Set the login status to logged out
|
||||
_isLoggedIn = false;
|
||||
debugPrint('Successfully logged out');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,31 @@ part of 'auth_store.dart';
|
|||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$AuthStore on AuthBase, Store {
|
||||
final _$_isLoggedInAtom = Atom(name: 'AuthBase._isLoggedIn');
|
||||
mixin _$AuthStore on _AuthBase, Store {
|
||||
Computed<Map<String, String>>? _$headersTwitchComputed;
|
||||
|
||||
@override
|
||||
Map<String, String> get headersTwitch => (_$headersTwitchComputed ??=
|
||||
Computed<Map<String, String>>(() => super.headersTwitch,
|
||||
name: '_AuthBase.headersTwitch'))
|
||||
.value;
|
||||
|
||||
final _$_tokenAtom = Atom(name: '_AuthBase._token');
|
||||
|
||||
@override
|
||||
String? get _token {
|
||||
_$_tokenAtom.reportRead();
|
||||
return super._token;
|
||||
}
|
||||
|
||||
@override
|
||||
set _token(String? value) {
|
||||
_$_tokenAtom.reportWrite(value, super._token, () {
|
||||
super._token = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_isLoggedInAtom = Atom(name: '_AuthBase._isLoggedIn');
|
||||
|
||||
@override
|
||||
bool get _isLoggedIn {
|
||||
|
|
@ -24,10 +47,61 @@ mixin _$AuthStore on AuthBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
final _$_tokenIsValidAtom = Atom(name: '_AuthBase._tokenIsValid');
|
||||
|
||||
@override
|
||||
bool get _tokenIsValid {
|
||||
_$_tokenIsValidAtom.reportRead();
|
||||
return super._tokenIsValid;
|
||||
}
|
||||
|
||||
@override
|
||||
set _tokenIsValid(bool value) {
|
||||
_$_tokenIsValidAtom.reportWrite(value, super._tokenIsValid, () {
|
||||
super._tokenIsValid = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_userAtom = Atom(name: '_AuthBase._user');
|
||||
|
||||
@override
|
||||
UserTwitch? get _user {
|
||||
_$_userAtom.reportRead();
|
||||
return super._user;
|
||||
}
|
||||
|
||||
@override
|
||||
set _user(UserTwitch? value) {
|
||||
_$_userAtom.reportWrite(value, super._user, () {
|
||||
super._user = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$initAsyncAction = AsyncAction('_AuthBase.init');
|
||||
|
||||
@override
|
||||
Future<void> init() {
|
||||
return _$initAsyncAction.run(() => super.init());
|
||||
}
|
||||
|
||||
final _$loginAsyncAction = AsyncAction('_AuthBase.login');
|
||||
|
||||
@override
|
||||
Future<void> login() {
|
||||
return _$loginAsyncAction.run(() => super.login());
|
||||
}
|
||||
|
||||
final _$logoutAsyncAction = AsyncAction('_AuthBase.logout');
|
||||
|
||||
@override
|
||||
Future<void> logout() {
|
||||
return _$logoutAsyncAction.run(() => super.logout());
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
|
||||
headersTwitch: ${headersTwitch}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue