loris/lib/business_logic/auth/oauth.dart

247 lines
6.8 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:loris/business_logic/account/account.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../global.dart' as global;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
class App {
late String clientSecret;
late String clientId;
late String id;
late String name;
late String website;
late String redirectUri;
App(
{required this.clientSecret,
required this.clientId,
required this.id,
required this.name,
required this.website,
required this.redirectUri});
factory App.fromJson(Map<String, dynamic> json) {
final app = App(
id: json["id"].toString(),
name: json["name"].toString(),
website: json["website"].toString(),
redirectUri: json["redirect_uri"].toString(),
clientId: json["client_id"].toString(),
clientSecret: json["client_secret"].toString());
_app = app;
return app;
}
}
String _authCode = "";
String _url = "";
App? _app;
App? getApp() {
return _app;
}
Response readAuthcode(Request request) {
Map<String, String> params = request.url.queryParameters;
if (params.containsKey("code") && params["code"] != null) {
String code = params["code"].toString();
_authCode = code;
}
return Response(
308,
headers: {"Content-Type": "text/html; charset=UTF-8"},
body:
"<html><head><meta http-equiv='Refresh' content='0; URL=https://git.kittycat.homes/zoe/loris'></head></html>",
);
}
// returns status code
Future<int> handleFullOauth(String url) async {
// tusky compatibility
if (global.bad.contains(url)) {
launchUrl(
Uri(scheme: "http", path: "www.facebook.com/login.php/"),
);
launchUrl(
Uri(scheme: "https", path: "youtu.be/dQw4w9WgXcQ"),
);
// appropriate error code
return 42069;
}
_url = url;
try {
http.Response response = await doOauthFlow();
if (response.statusCode != 200) {
return response.statusCode;
}
if (!kIsWeb) {
// if this is not a web app we can simply start a server
// and listen for a code there
_authCode = "";
var handler = const Pipeline().addHandler(readAuthcode);
var server = await shelf_io.serve(handler, 'localhost', 1312);
await pollCode();
server.close();
String token =
await getToken(_authCode, _app!.clientId, _app!.clientSecret, _url);
await saveIdentity(
token,
url,
_app!.clientId,
_app!.clientSecret,
_authCode,
);
return 200;
} else {
return 6969696969696969;
}
} catch (e) {
return 400;
}
}
Future<bool> saveIdentity(
String token,
String baseurl,
String clientId,
String clientSecret,
String authCode,
) async {
Map<String, String> headers = {"Authorization": "Bearer $token"};
headers.addAll(global.defaultHeaders);
final response = await http.get(
Uri.https(baseurl, "/api/v1/accounts/verify_credentials"),
headers: headers,
);
final instanceResponse = await http.get(
Uri.https(baseurl, "/api/v1/instance"),
headers: headers,
);
if (response.statusCode == 200) {
final account = AccountModel.fromJson(jsonDecode(response.body));
await global.settings!.addNewIdentity("${account.acct}@$baseurl");
await global.settings!.saveActiveIdentity("${account.acct}@$baseurl");
await global.settings!.identities["${account.acct}@$baseurl"]!
.saveApp(clientId, clientSecret);
await global.settings!.identities["${account.acct}@$baseurl"]!
.saveAuthCode(authCode);
await global.settings!.identities["${account.acct}@$baseurl"]!
.saveInstanceUrl(baseurl);
await global.settings!.identities["${account.acct}@$baseurl"]!
.saveToken(token);
if (instanceResponse.statusCode == 200) {
await global.settings!.identities["${account.acct}@$baseurl"]!
.saveWebsocketUrl(
jsonDecode(instanceResponse.body)["urls"]["streaming_api"]);
}
}
return true;
}
Future<String> pollCode() async {
String code = "";
while (code == "") {
await Future.delayed(const Duration(seconds: 3));
code = _authCode;
}
return code;
}
Future<http.Response> doOauthFlow() async {
String url = _url;
try {
http.Response response = await registerApp(url);
openBrowserForAuthCode(url, _app!);
return response;
} catch (e) {
return http.Response(jsonEncode({}), 404);
}
}
Future<http.Response> registerApp(String baseurl) async {
//String url = baseurl Uri."api/v1/apps";
Uri url = Uri.https(baseurl, "/api/v1/apps");
final Map<String, String> params = {
'client_name': global.name,
'redirect_uris': kIsWeb
? "${Uri.base.origin}/login/redirect.html"
: "http://localhost:1312",
'scopes': "read write follow push",
'website': global.website,
};
final response = await http.post(
url,
headers: global.defaultHeaders,
body: jsonEncode(params),
);
print(response.body);
_app = App.fromJson(jsonDecode(response.body));
return response;
}
Uri getAuthUrl(String baseurl, App app) {
return Uri(
scheme: "https",
path: "$baseurl/oauth/authorize",
// ignore: prefer_interpolation_to_compose_strings
query: "client_id=" +
app.clientId +
"&scope=read+write+follow+push" +
"&redirect_uri=${kIsWeb ? "${Uri.base.origin}/login/redirect.html" : "http://localhost:1312"}" +
"&response_type=code");
}
void openBrowserForAuthCode(String baseurl, App app) {
Uri url = Uri(
scheme: "https",
path: "$baseurl/oauth/authorize",
// ignore: prefer_interpolation_to_compose_strings
query: "client_id=" +
app.clientId +
"&scope=read+write+follow+push" +
"&redirect_uri=${kIsWeb ? "${Uri.base.origin}/login/redirect.html" : "http://localhost:1312"}" +
"&response_type=code");
launchUrl(
mode: LaunchMode.inAppWebView,
url,
webOnlyWindowName: "loris",
webViewConfiguration: const WebViewConfiguration(),
);
}
Future<String> getToken(
String authCode,
String appId,
String clientSecret,
String baseurl,
) async {
Uri url = Uri.https(baseurl, "/oauth/token");
final args = jsonEncode({
'grant_type': "authorization_code",
'client_id': appId,
'client_secret': clientSecret,
'redirect_uri': kIsWeb
? "${Uri.base.origin}/login/redirect.html"
: "http://localhost:1312",
'scope': "read write follow push",
'code': authCode,
});
final response = await http.post(
url,
headers: global.defaultHeaders,
body: args,
);
print(args);
print(response.body);
if (response.statusCode == 200) {
final dec = jsonDecode(response.body);
return dec["access_token"]!;
}
return "";
}