import 'dart:convert'; import 'dart:io'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:result_monad/result_monad.dart'; import 'package:uuid/uuid.dart'; import '../exec_error.dart'; import 'credentials_intf.dart'; class OAuthCredentials implements ICredentials { static final _logger = Logger('$OAuthCredentials'); String clientId; String clientSecret; final String redirectUrl; final String redirectScheme; String accessToken; @override final String serverName; @override final String id; OAuthCredentials({ required this.clientId, required this.clientSecret, required this.redirectUrl, required this.redirectScheme, required this.accessToken, required this.serverName, required this.id, }); factory OAuthCredentials.bootstrap(String serverName) { final redirectScheme = Platform.isWindows || Platform.isLinux ? 'http://localhost:43824' : 'relatica'; final redirectUrl = Platform.isWindows || Platform.isLinux ? redirectScheme : '$redirectScheme:/'; return OAuthCredentials( clientId: '', clientSecret: '', redirectUrl: redirectUrl, redirectScheme: redirectScheme, accessToken: '', serverName: serverName, id: const Uuid().v4(), ); } @override String get authHeaderValue => 'Bearer $accessToken'; @override FutureResult signIn() async { final result = await _getIds() .andThenAsync((_) async => await _login()) .andThenSuccessAsync((_) async => this); return result.execErrorCast(); } @override Map toJson() => { 'clientId': clientId, 'clientSecret': clientSecret, 'redirectUrl': redirectUrl, 'redirectScheme': redirectScheme, 'accessToken': accessToken, 'serverName': serverName, 'id': id, }; static OAuthCredentials fromJson(Map json) => OAuthCredentials( clientId: json['clientId'], clientSecret: json['clientSecret'], redirectUrl: json['redirectUrl'], redirectScheme: json['redirectScheme'], accessToken: json['accessToken'], serverName: json['serverName'], id: json['id'], ); FutureResult _getIds() async { if (clientId.isNotEmpty || clientSecret.isNotEmpty) { _logger.info( 'Client ID and Client Secret are already set, skipping ID fetching'); return Result.ok(true); } final idEndpoint = Uri.parse('https://$serverName/api/v1/apps'); final response = await http.post(idEndpoint, body: { 'client_name': 'Relatica', 'redirect_uris': redirectUrl, 'scopes': 'read write follow push', 'website': 'https://myportal.social', }); if (response.statusCode != 200) { _logger.severe('Error: ${response.statusCode}: ${response.body}'); return buildErrorResult( type: ErrorType.serverError, message: 'Error: ${response.statusCode}: ${response.body}', ); } final json = jsonDecode(response.body); clientId = json['client_id']; clientSecret = json['client_secret']; return Result.ok(true); } FutureResult _login() async { if (accessToken.isNotEmpty) { _logger.info('Already have access token, skipping'); return Result.ok(true); } // Construct the url final url = Uri.https(serverName, '/oauth/authorize', { 'response_type': 'code', 'client_id': clientId, 'redirect_uri': redirectUrl, 'scope': 'read write follow push', }); try { final result = await FlutterWebAuth2.authenticate( url: url.toString(), callbackUrlScheme: redirectScheme); final code = Uri.parse(result).queryParameters['code']; if (code == null) { _logger.severe( 'Error code was not returned with the query parameters: $result'); return buildErrorResult( type: ErrorType.serverError, message: 'Error getting the response code during authentication', ); } final url2 = Uri.parse('https://$serverName/oauth/token'); final body = { 'client_id': clientId, 'client_secret': clientSecret, 'redirect_uri': redirectUrl, 'grant_type': 'authorization_code', 'code': code, }; final response = await http.post(url2, body: body); if (response.statusCode != 200) { _logger.severe('Error: ${response.statusCode}: ${response.body}'); return buildErrorResult( type: ErrorType.serverError, message: 'Error: ${response.statusCode}: ${response.body}', ); } accessToken = jsonDecode(response.body)['access_token']; } catch (e) { _logger.severe('Exception while Doing OAuth Process: $e'); return buildErrorResult( type: ErrorType.serverError, message: 'Exception while Doing OAuth Process: $e', ); } return Result.ok(true); } }