make identities less annoying

This commit is contained in:
zoe 2022-08-14 13:32:26 +02:00
parent 84d85d70e3
commit a2b2b51517
6 changed files with 117 additions and 85 deletions

View File

@ -1,6 +1,7 @@
import 'dart:convert';
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';
@ -28,17 +29,20 @@ class App {
redirectUri: json["redirect_uri"].toString(),
clientId: json["client_id"].toString(),
clientSecret: json["client_secret"].toString());
global.settings!.identities[global.settings!.activeIdentity]!.saveApp(app);
_app = app;
return app;
}
}
String _authCode = "";
String _url = "";
App? _app;
Response readAuthcode(Request request) {
Map<String, String> params = request.url.queryParameters;
if (params.containsKey("code") && params["code"] != null) {
String code = params["code"].toString();
global.settings!.identities[global.settings!.activeIdentity]!
.saveAuthCode(code);
_authCode = code;
}
return Response(308,
headers: {"Content-Type": "text/html; charset=UTF-8"},
@ -47,39 +51,72 @@ Response readAuthcode(Request request) {
}
// returns status code
Future<int> handleFullOauth() async {
Future<int> handleFullOauth(String url) async {
_url = url;
try {
http.Response response = await doOauthFlow();
if (response.statusCode != 200) {
return response.statusCode;
}
await global.settings!.identities[global.settings!.activeIdentity]!
.saveAuthCode("");
_authCode = "";
var handler = const Pipeline().addHandler(readAuthcode);
var server = await shelf_io.serve(handler, 'localhost', 1312);
await pollCode();
server.close();
await refreshToken();
String token =
await getToken(_authCode, _app!.clientId, _app!.clientSecret, _url);
await saveIdentity(
token,
url,
_app!,
_authCode,
);
return 200;
} catch (e) {
return 400;
}
}
Future<bool> saveIdentity(
String token,
String baseurl,
App app,
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,
);
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(app);
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);
}
return true;
}
Future<String> pollCode() async {
String code = "";
while (code == "") {
await Future.delayed(const Duration(seconds: 3));
code =
global.settings!.identities[global.settings!.activeIdentity]!.authCode;
code = _authCode;
}
return code;
}
Future<http.Response> doOauthFlow() async {
String url =
global.settings!.identities[global.settings!.activeIdentity]!.instanceUrl;
String url = _url;
try {
http.Response response = await registerApp(url);
openBrowserForAuthCode(url, App.fromJson(jsonDecode(response.body)));
@ -123,14 +160,12 @@ void openBrowserForAuthCode(String baseurl, App app) {
launchUrl(url);
}
Future<int> refreshToken() async {
print("refreshing_token");
final activeId = global.settings!.activeIdentity;
final authCode = global.settings!.identities[activeId]!.authCode;
final appId = global.settings!.identities[activeId]!.clientId;
final clientSecret = global.settings!.identities[activeId]!.clientSecret;
final baseurl = global.settings!.identities[activeId]!.instanceUrl;
Future<String> getToken(
String authCode,
String appId,
String clientSecret,
String baseurl,
) async {
Uri url = Uri.https(baseurl, "/oauth/token");
final response = await http.post(
url,
@ -146,9 +181,7 @@ Future<int> refreshToken() async {
);
if (response.statusCode == 200) {
final dec = jsonDecode(response.body);
final accessToken = dec["access_token"]!;
await global.settings!.identities[global.settings!.activeIdentity]!
.saveToken(accessToken);
return dec["access_token"]!;
}
return response.statusCode;
return "";
}

View File

@ -36,11 +36,11 @@ class AccountSettings {
}
Future<void> delete() async {
prefs.remove("$identity.$instanceUrlKey");
prefs.remove("$identity.$authCodeKey");
prefs.remove("$identity.$clientSecretKey");
prefs.remove("$identity.$clientIdKey");
prefs.remove("$identity.$tokenKey");
await prefs.remove("$identity.$instanceUrlKey");
await prefs.remove("$identity.$authCodeKey");
await prefs.remove("$identity.$clientSecretKey");
await prefs.remove("$identity.$clientIdKey");
await prefs.remove("$identity.$tokenKey");
}
Future<bool> saveInstanceUrl(String url) async {
@ -56,8 +56,8 @@ class AccountSettings {
Future<void> saveApp(oauth.App app) async {
clientId = app.clientId;
clientSecret = app.clientSecret;
prefs.setString("$identity.$clientSecretKey", app.clientSecret);
prefs.setString("$identity.$clientIdKey", app.clientId);
await prefs.setString("$identity.$clientSecretKey", app.clientSecret);
await prefs.setString("$identity.$clientIdKey", app.clientId);
}
Future<bool> saveToken(String token) async {
@ -126,9 +126,12 @@ class Settings {
}
Future<bool> removeIdentity(String key) async {
identities[key]!.delete();
await identities[key]!.delete();
identities.remove(key);
return prefs.setStringList(
if (identities.isNotEmpty) {
activeIdentity = identities.keys.first;
}
return await prefs.setStringList(
identitiesKey,
identities.keys.toList(),
);

View File

@ -20,7 +20,7 @@ void main() async {
// check if all information is available
if (global.settings!.identities.isNotEmpty) {
await oauth.refreshToken();
// await oauth.refreshToken();
}
runApp(const Loris());
}
@ -36,23 +36,21 @@ class _LorisState extends State<Loris> {
@override
Widget build(BuildContext context) {
LocalJsonLocalization.delegate.directories = ['lib/i18n'];
return Phoenix(
child: MaterialApp(
theme: theme,
locale: activeLocale,
supportedLocales: global.availableLocales,
localizationsDelegates: [
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
LocalJsonLocalization.delegate,
],
initialRoute: global.settings!.identities.isEmpty ? "/login" : "/",
routes: {
'/': (context) => const MainScaffold(),
'/login': (context) => const Login(),
},
),
return MaterialApp(
theme: theme,
locale: activeLocale,
supportedLocales: global.availableLocales,
localizationsDelegates: [
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
LocalJsonLocalization.delegate,
],
initialRoute: global.settings!.identities.isEmpty ? "/login" : "/",
routes: {
'/': (context) => const MainScaffold(),
'/login': (context) => const Login(),
},
);
}
}

View File

@ -31,7 +31,7 @@ class LoginForm extends StatefulWidget {
class _LoginFormState extends State<LoginForm> {
final formKey = GlobalKey<FormState>();
String identity = "";
String instanceUrl = "";
@override
Widget build(BuildContext context) {
return Form(
@ -45,10 +45,7 @@ class _LoginFormState extends State<LoginForm> {
Text("greeting".i18n(), style: Theme.of(context).textTheme.headline1),
TextFormField(
onSaved: (value) async {
await global.settings!.addNewIdentity(identity);
await global.settings!.saveActiveIdentity(identity);
await global.settings!.identities[identity]!
.saveInstanceUrl(value!);
instanceUrl = value ?? "";
},
decoration: InputDecoration(
labelText: "instance-url".i18n(),
@ -58,11 +55,6 @@ class _LoginFormState extends State<LoginForm> {
),
autofocus: true,
),
TextFormField(
onChanged: ((value) {
identity = value;
}),
),
TextButton.icon(
onPressed: () {
bool isValid = formKey.currentState!.validate();
@ -79,7 +71,7 @@ class _LoginFormState extends State<LoginForm> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AuthPage(),
builder: (context) => AuthPage(url: instanceUrl),
));
}
},
@ -95,8 +87,8 @@ class _LoginFormState extends State<LoginForm> {
page that handles authenticating user
*/
class AuthPage extends StatefulWidget {
const AuthPage({Key? key}) : super(key: key);
const AuthPage({required this.url, Key? key}) : super(key: key);
final String url;
@override
State<AuthPage> createState() => _AuthPageState();
}
@ -111,7 +103,7 @@ class _AuthPageState extends State<AuthPage> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FutureBuilder<int>(
future: oauth.handleFullOauth(),
future: oauth.handleFullOauth(widget.url),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("login-failed-snackbar-text".i18n());
@ -131,11 +123,14 @@ class _AuthPageState extends State<AuthPage> {
}
}
return TextButton.icon(
onPressed: () {
Navigator.pushReplacementNamed(context, "/");
},
icon: const Icon(Icons.arrow_forward),
label: Text("confirm".i18n()));
onPressed: () async {
await Navigator.pushReplacementNamed(context, "/");
},
icon: const Icon(Icons.arrow_forward),
label: Text(
"confirm".i18n(),
),
);
} else {
return const CircularProgressIndicator();
}
@ -145,11 +140,14 @@ class _AuthPageState extends State<AuthPage> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton.icon(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
label: Text("back-button".i18n())),
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
label: Text(
"back-button".i18n(),
),
),
],
)
],

View File

@ -1,7 +1,4 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_phoenix/flutter_phoenix.dart';
import 'package:localization/localization.dart';
import '../../global.dart' as global;
@ -30,12 +27,9 @@ class LogoutButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
String url = global
.settings!.identities[global.settings!.activeIdentity]!.instanceUrl;
return Row(
children: [
Text(url + ": " + identity),
SelectableText(identity),
TextButton.icon(
onPressed: () {
logout(context, identity);
@ -47,7 +41,13 @@ class LogoutButton extends StatelessWidget {
}
}
void logout(context, String identity) async {
global.settings!.removeIdentity(identity);
Phoenix.rebirth(context);
void addNewIdentity(context) {
Navigator.of(context).pushReplacementNamed("/login");
}
void logout(context, String identity) async {
await global.settings!.removeIdentity(identity);
if (global.settings!.identities.isEmpty) {
Navigator.of(context).pushReplacementNamed("/login");
}
}

View File

@ -216,7 +216,7 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
platform:
dependency: transitive
description: