Rewrote autologin to default to authorized

This is prevents the current route from being reset,
when reloading the tab
and also prevents the welcome screen from showing
(when running on slow connections)

NOTE: This also means that the home screen will be loaded
even if the client has never been logged in.
This means that some functions might return null
This commit is contained in:
Jakob Meier 2023-03-18 20:24:48 +01:00
parent 63155fc6bb
commit 31a84b5ec7
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
5 changed files with 109 additions and 78 deletions

View file

@ -1,7 +1,7 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import './resolve_url.dart'; import './resolve_url.dart';
import './storage.dart'; import './user.dart';
enum Result { enum Result {
ok, ok,
@ -29,11 +29,11 @@ Future<Response> postWithCreadentials(
{required OutbagServer target, {required OutbagServer target,
String path = '', String path = '',
required Map<String, dynamic> body, required Map<String, dynamic> body,
required LoginDetails credentials}) async { required User credentials}) async {
Map<String, String> headers = { Map<String, String> headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
'Authorization': 'Authorization':
'Digest name=${credentials.username} server=${target.base} accountKey=${credentials.password}' 'Digest name=${credentials.username} server=${target.tag} accountKey=${credentials.password}'
}; };
return await usePostApi( return await usePostApi(
target: target, path: path, headers: headers, body: body); target: target, path: path, headers: headers, body: body);

View file

@ -2,6 +2,8 @@ import 'package:localstore/localstore.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'package:outbag_app/main.dart';
const String wellKnownPath = "/.well-known/outbag/server"; const String wellKnownPath = "/.well-known/outbag/server";
const int defaultPort = 7223; const int defaultPort = 7223;
@ -21,7 +23,7 @@ class OutbagServer {
final String tag; final String tag;
const OutbagServer( const OutbagServer(
{required this.host, {required this.host,
required this.port, required this.port,
required this.path, required this.path,
required this.tag}); required this.tag});
@ -35,23 +37,28 @@ class OutbagServer {
).toString(); ).toString();
} }
void toDisk() async { factory OutbagServer.fromMap(Map<String, dynamic> data) {
return OutbagServer(
tag: data['tag'],
host: data['host'],
path: data['path'],
port: data['port']);
}
Map<String, dynamic> toMap() {
return {'host': host, 'port': port, 'path': path, 'tag': tag};
}
Future<void> toDisk() async {
final db = Localstore.instance; final db = Localstore.instance;
await db await db.collection('meta').doc('server').set(toMap());
.collection('meta')
.doc('server')
.set({'host': host, 'port': port, 'path': path, 'tag': tag});
} }
static Future<OutbagServer> fromDisk() async { static Future<OutbagServer> fromDisk() async {
final db = Localstore.instance; final db = Localstore.instance;
final data = await db.collection('meta').doc('server').get(); final data = await db.collection('meta').doc('server').get();
return OutbagServer( return OutbagServer.fromMap(data!);
tag: data?['tag'],
host: data?['host'],
path: data?['path'],
port: data?['port']);
} }
} }
@ -63,18 +70,18 @@ Future<OutbagServer> getOutbagServerUrl(String tag) async {
try { try {
final uri = await fetchWellKnown(rootUri.host, rootUri.port); final uri = await fetchWellKnown(rootUri.host, rootUri.port);
return OutbagServer( return OutbagServer(
host: rootUri.host, host: rootUri.host,
port: uri.port, port: uri.port,
path: uri.path, path: uri.path,
tag: onRoot ? rootUri.host : '$rootUri.host:$uri.port'); tag: onRoot ? rootUri.host : '${rootUri.host}:${uri.port}');
} catch (_) { } catch (_) {
if (onRoot) { if (onRoot) {
final uri = await fetchWellKnown(rootUri.host, defaultPort); final uri = await fetchWellKnown(rootUri.host, defaultPort);
return OutbagServer( return OutbagServer(
host: rootUri.host, host: rootUri.host,
port: uri.port, port: uri.port,
path: uri.path, path: uri.path,
tag: onRoot ? rootUri.host : '$rootUri.host:$uri.port'); tag: onRoot ? rootUri.host : '${rootUri.host}:${uri.port}');
} }
} }
throw Error(); throw Error();

View file

@ -1,38 +0,0 @@
import 'package:localstore/localstore.dart';
import './resolve_url.dart';
class LoginDetails {
const LoginDetails(
{required this.username, required this.password, required this.server});
final String username;
final String password;
final OutbagServer server;
void toDisk() async {
final db = Localstore.instance;
await db
.collection('meta')
.doc('auth')
.set({'username': username, 'password': password});
server.toDisk();
}
static Future<LoginDetails> fromDisk() async {
final db = Localstore.instance;
final data = await db.collection('meta').doc('auth').get();
final server = await OutbagServer.fromDisk();
return LoginDetails(
username: data?['username'],
password: data?['password'],
server: server,
);
}
}
// obtain room list
Future<Map<String, dynamic>?> getRooms() async {
final db = Localstore.instance;
return await db.collection('rooms').get();
}

38
lib/backend/user.dart Normal file
View file

@ -0,0 +1,38 @@
import 'package:localstore/localstore.dart';
import './resolve_url.dart';
class User {
const User(
{required this.username, required this.password, required this.server});
final String username;
final String password;
final OutbagServer server;
Future<void> toDisk() async {
final db = Localstore.instance;
await db
.collection('meta')
.doc('auth')
.set({'username': username, 'password': password});
await server.toDisk();
}
static Future<User> fromDisk() async {
final db = Localstore.instance;
final data = await db.collection('meta').doc('auth').get();
final server = await OutbagServer.fromDisk();
return User(
username: data?['username'],
password: data?['password'],
server: server,
);
}
static listen(Function(Map<String, dynamic>) cb) async {
final db = Localstore.instance;
final stream = db.collection('meta').stream;
stream.listen(cb);
}
}

View file

@ -1,22 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outbag_app/backend/storage.dart'; import 'package:outbag_app/backend/user.dart';
import 'package:outbag_app/screens/room/new.dart';
import './screens/home.dart'; import './screens/home.dart';
import './screens/welcome.dart'; import './screens/welcome.dart';
import './screens/room.dart'; import './screens/room.dart';
import './screens/auth.dart'; import './screens/auth.dart';
import './backend/request.dart'; import './backend/request.dart';
import 'package:routemaster/routemaster.dart'; import 'package:routemaster/routemaster.dart';
import 'package:localstore/localstore.dart';
// routes when user is logged in
final routesLoggedIn = RouteMap(routes: {
'/': (_) => const MaterialPage(child: HomePage()),
'/r/:server/:tag/:page': (info) => MaterialPage(
child: RoomPage(info.pathParameters['server'] ?? "",
info.pathParameters['tag'] ?? "",
page: info.pathParameters['page'] ?? ""),
)
}, onUnknownRoute: (_) => const Redirect('/'));
// routes when user is not logged in // routes when user is not logged in
final routesUnauthorized = RouteMap(routes: { final routesUnauthorized = RouteMap(routes: {
'/welcome/': (_) => const MaterialPage(child: WelcomePage()), '/welcome/': (_) => const MaterialPage(child: WelcomePage()),
@ -24,7 +15,23 @@ final routesUnauthorized = RouteMap(routes: {
'/signupOTA': (_) => '/signupOTA': (_) =>
const MaterialPage(child: AuthPage(mode: Mode.signupOTA)), const MaterialPage(child: AuthPage(mode: Mode.signupOTA)),
'/signin': (_) => const MaterialPage(child: AuthPage(mode: Mode.signin)), '/signin': (_) => const MaterialPage(child: AuthPage(mode: Mode.signin)),
}, onUnknownRoute: (_) => const Redirect('/welcome')); }, onUnknownRoute: (_) => const MaterialPage(child: WelcomePage()));
// routes when user is logged in
final routesLoggedIn = RouteMap(routes: {
'/': (_) => const MaterialPage(child: HomePage()),
'/new': (_) => const MaterialPage(child: NewRoomPage()),
'/r/:server/:tag/:page?': (info) => MaterialPage(
child: RoomPage(info.pathParameters['server'] ?? "",
info.pathParameters['tag'] ?? "",
page: info.pathParameters['page'] ?? ""),
),
'/r/:server/:tag/': (info) => MaterialPage(
child: RoomPage(info.pathParameters['server'] ?? "",
info.pathParameters['tag'] ?? "",
page: 'list'),
),
}, onUnknownRoute: (_) => const Redirect('/'));
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -39,7 +46,10 @@ class OutbagApp extends StatefulWidget {
} }
class _OutbagAppState extends State { class _OutbagAppState extends State {
bool isAuthorized = false; // assume user is logged in
// unless not userdata is found
// or the userdata turns out to be wrong
bool isAuthorized = true;
@override @override
void initState() { void initState() {
@ -49,8 +59,18 @@ class _OutbagAppState extends State {
// with existing details // with existing details
(() async { (() async {
User credentials;
try {
credentials = await User.fromDisk();
} catch (_) {
// invalid credentials
// log out
setState(() {
isAuthorized = false;
});
return;
}
try { try {
final credentials = await LoginDetails.fromDisk();
final resp = await postUnauthorized( final resp = await postUnauthorized(
target: credentials.server, target: credentials.server,
path: 'signin', path: 'signin',
@ -63,6 +83,12 @@ class _OutbagAppState extends State {
setState(() { setState(() {
isAuthorized = true; isAuthorized = true;
}); });
} else {
// credentials are wrong
// log out
setState(() {
isAuthorized = true;
});
} }
} catch (_) { } catch (_) {
// user is currently offline // user is currently offline
@ -76,11 +102,9 @@ class _OutbagAppState extends State {
})(); })();
// wait for user to be authorized // wait for user to be authorized
final db = Localstore.instance; User.listen((data) async {
final stream = db.collection('meta').stream;
stream.listen((data) async {
try { try {
await LoginDetails.fromDisk(); await User.fromDisk();
setState(() { setState(() {
isAuthorized = true; isAuthorized = true;
}); });