363 lines
15 KiB
Dart
363 lines
15 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:footer/footer.dart';
|
|
import 'package:footer/footer_view.dart';
|
|
import 'package:libac_dart/nbt/NbtIo.dart';
|
|
import 'package:libac_dart/nbt/NbtUtils.dart';
|
|
import 'package:libac_dart/nbt/impl/CompoundTag.dart';
|
|
import 'package:libac_dart/nbt/impl/IntTag.dart';
|
|
import 'package:libac_dart/nbt/impl/StringTag.dart';
|
|
import 'package:libac_dart/utils/uuid/UUID.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:zontreck/Constants.dart';
|
|
import 'package:zontreck/Packets.dart';
|
|
import 'package:zontreck/Settings.dart';
|
|
import 'package:zontreck/pages/opensim/Inventory.dart';
|
|
|
|
class User {
|
|
UUID ID;
|
|
String FirstName;
|
|
String LastName;
|
|
int createdAt;
|
|
String userTitle;
|
|
bool active;
|
|
UUID loginToken;
|
|
|
|
User(
|
|
{required this.ID,
|
|
required this.FirstName,
|
|
required this.LastName,
|
|
required this.createdAt,
|
|
required this.userTitle,
|
|
required this.active,
|
|
required this.loginToken});
|
|
|
|
static User parseJson(Map<String, dynamic> map) {
|
|
return User(
|
|
ID: UUID.parse(map['id'] as String),
|
|
FirstName: map['first'] as String,
|
|
LastName: map['last'] as String,
|
|
createdAt: map['rez'] as int,
|
|
userTitle: map['title'] as String,
|
|
active: map['active'] as bool,
|
|
loginToken: UUID.parse(map['token'] as String));
|
|
}
|
|
|
|
String encode() {
|
|
return json.encode({
|
|
"id": ID.toString(),
|
|
"first": FirstName,
|
|
"last": LastName,
|
|
"rez": createdAt,
|
|
"title": userTitle,
|
|
"active": active,
|
|
"token": loginToken.toString()
|
|
});
|
|
}
|
|
|
|
CompoundTag save() {
|
|
CompoundTag tag = CompoundTag();
|
|
tag.put("id", StringTag.valueOf(ID.toString()));
|
|
tag.put("first", StringTag.valueOf(FirstName));
|
|
tag.put("last", StringTag.valueOf(LastName));
|
|
tag.put("rez", IntTag.valueOf(createdAt));
|
|
tag.put("title", StringTag.valueOf(userTitle));
|
|
NbtUtils.writeBoolean(tag, "active", active);
|
|
tag.put("token", StringTag.valueOf(loginToken.toString()));
|
|
|
|
return tag;
|
|
}
|
|
|
|
static User load(CompoundTag tag) {
|
|
return User(
|
|
ID: UUID.parse(tag.get("id")!.asString()),
|
|
FirstName: tag.get("first")!.asString(),
|
|
LastName: tag.get("last")!.asString(),
|
|
createdAt: tag.get("rez")!.asInt(),
|
|
userTitle: tag.get("title")!.asString(),
|
|
active: NbtUtils.readBoolean(tag, "active"),
|
|
loginToken: UUID.parse(tag.get("token")!.asString()));
|
|
}
|
|
}
|
|
|
|
class OpenSimPage extends StatefulWidget {
|
|
const OpenSimPage({super.key});
|
|
|
|
@override
|
|
OpenSimPageState createState() => OpenSimPageState();
|
|
}
|
|
|
|
class OpenSimPageState extends State<OpenSimPage> {
|
|
Settings settings = Settings();
|
|
|
|
TextEditingController databaseHostController = TextEditingController();
|
|
TextEditingController databaseUsernameController = TextEditingController();
|
|
TextEditingController databasePasswordController = TextEditingController();
|
|
TextEditingController databaseNameController = TextEditingController();
|
|
TextEditingController PSKController = TextEditingController();
|
|
String clientPSK = "";
|
|
String PSKHash = "";
|
|
|
|
bool polling = true;
|
|
bool activating = false;
|
|
|
|
@override
|
|
Future<void> didChangeDependencies() async {
|
|
var reply = await settings.sendPacketToEndpoint(
|
|
APIEndpoint.SetupCheck, NullPacket());
|
|
var simpleReply = reply as S2CSimpleReplyPacket;
|
|
if (simpleReply.done) {
|
|
settings.OpenSimSetupCompleted = true;
|
|
} else {
|
|
settings.OpenSimSetupCompleted = false;
|
|
}
|
|
|
|
if (!settings.loggedIn) {
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
if (prefs.containsKey("settings")) {
|
|
String encoded = prefs.getString("settings")!;
|
|
CompoundTag tag = await NbtIo.readBase64String(encoded) as CompoundTag;
|
|
if (tag.containsKey("user")) {
|
|
settings.currentUser = User.load(tag.get("user") as CompoundTag);
|
|
settings.loggedIn = true;
|
|
}
|
|
|
|
// Validate current session
|
|
var reply = await settings.sendPacketToEndpoint(
|
|
APIEndpoint.ValidateSession,
|
|
C2SSessionCheckPacket(
|
|
sessionToken: settings.currentUser!.loginToken))
|
|
as S2CSessionCheckPacket;
|
|
if (reply.valid) {
|
|
// We're good to continue loading this user
|
|
} else {
|
|
settings.currentUser = null;
|
|
prefs.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
var pong = await settings.sendPacketToEndpoint(
|
|
APIEndpoint.Ping, NullPacket()) as S2CPongPacket;
|
|
|
|
if (settings.currentUser != null) {
|
|
settings.userName =
|
|
"${settings.currentUser!.FirstName}.${settings.currentUser!.LastName}";
|
|
settings.displayName = settings.userName;
|
|
}
|
|
settings.totalGridUsers = pong.totalUsers;
|
|
|
|
setState(() {
|
|
polling = false;
|
|
});
|
|
|
|
if (settings.loggedIn && !settings.currentUser!.active && !activating) {
|
|
activating = true;
|
|
await showDialog(
|
|
context: context,
|
|
builder: (B) {
|
|
return const CreateInventoryPopup();
|
|
});
|
|
activating = false;
|
|
// Force user to re-login
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
prefs.clear();
|
|
setState(() {
|
|
settings.loggedIn = false;
|
|
settings.currentUser = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: Constants.TITLEBAR_COLOR,
|
|
title: const Text("Zontreck.com - OpenSim Manager"),
|
|
),
|
|
body: FooterView(
|
|
footer: Footer(
|
|
alignment: Alignment.center,
|
|
backgroundColor: ThemeData.dark().focusColor,
|
|
child:
|
|
const Text("${Constants.COPYRIGHT}\n${Constants.VERSION}")),
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: SingleChildScrollView(
|
|
child: polling
|
|
? const Column(
|
|
children: [
|
|
ListTile(
|
|
title: Text("Please wait... downloading content"),
|
|
tileColor: Constants.TITLEBAR_COLOR,
|
|
)
|
|
],
|
|
)
|
|
: settings.OpenSimSetupCompleted
|
|
? Column(
|
|
children: [
|
|
ListTile(
|
|
title: Text(
|
|
"There are ${settings.totalGridUsers} users registered with this grid",
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
ListTile(
|
|
title: Text(
|
|
settings.loggedIn
|
|
? "Welcome, ${settings.displayName}"
|
|
: "You are not currently logged in",
|
|
),
|
|
),
|
|
settings.loggedIn
|
|
? Column(
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
settings.loggedIn = false;
|
|
|
|
var loginToken = settings
|
|
.currentUser!.loginToken;
|
|
settings.currentUser = null;
|
|
|
|
await settings
|
|
.sendPacketToEndpoint(
|
|
APIEndpoint.Logout,
|
|
C2SLogoutPacket(
|
|
ID: loginToken));
|
|
|
|
SharedPreferences prefs =
|
|
await SharedPreferences
|
|
.getInstance();
|
|
prefs.clear();
|
|
|
|
didChangeDependencies();
|
|
},
|
|
child: const Text("LOGOUT"))
|
|
],
|
|
)
|
|
: Center(
|
|
child: Row(
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
await Navigator.pushNamed(
|
|
context, "/opensim/login");
|
|
|
|
didChangeDependencies();
|
|
},
|
|
child: const Text("Login")),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
await Navigator.pushNamed(
|
|
context, "/opensim/register");
|
|
didChangeDependencies();
|
|
},
|
|
child:
|
|
const Text("Register Account"))
|
|
],
|
|
))
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
const ListTile(
|
|
title: Text("Initial Setup Required"),
|
|
subtitle: Text(
|
|
"Please use the same database/user as robust's database\n\nNOTE: Only MySQL/MariaDB is supported by this interface"),
|
|
tileColor: Constants.TITLEBAR_COLOR,
|
|
),
|
|
ListTile(
|
|
title: const Text("Database Host"),
|
|
subtitle: TextField(
|
|
controller: databaseHostController,
|
|
decoration: const InputDecoration(
|
|
hintText: "example.com:3306"),
|
|
),
|
|
),
|
|
ListTile(
|
|
title: const Text("Database Username"),
|
|
subtitle: TextField(
|
|
controller: databaseUsernameController,
|
|
decoration: const InputDecoration(
|
|
hintText: "Username"),
|
|
),
|
|
),
|
|
ListTile(
|
|
title: const Text("Database Password"),
|
|
subtitle: TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: "****", hintMaxLines: 1),
|
|
obscureText: true,
|
|
obscuringCharacter: "*",
|
|
controller: databasePasswordController,
|
|
),
|
|
),
|
|
ListTile(
|
|
title: const Text("Database Name"),
|
|
subtitle: TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: "acwi", hintMaxLines: 1),
|
|
controller: databaseNameController,
|
|
),
|
|
),
|
|
const ListTile(
|
|
title: Text(
|
|
"For the PreShared Secret, please enter any text you wish. This is hashed 8192 times for the server key. And an additional 16384 times for the client, and any derived key thereafter"),
|
|
tileColor: Constants.TITLEBAR_COLOR,
|
|
),
|
|
ListTile(
|
|
title: const Text("PreShared Secret"),
|
|
subtitle: TextField(
|
|
controller: PSKController,
|
|
decoration: const InputDecoration(
|
|
hintText:
|
|
"Pre-Shared Key. Some text that gets hashed several thousand times to create a server and client key"),
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
PSKHash = await settings
|
|
.hashPSK(PSKController.text);
|
|
|
|
clientPSK = await settings.createDerivedPSK(
|
|
PSKHash, "client");
|
|
|
|
C2SPerformSetupPacket packet =
|
|
C2SPerformSetupPacket(
|
|
PSK: PSKHash,
|
|
ClientPSK: clientPSK,
|
|
host: databaseHostController.text,
|
|
user:
|
|
databaseUsernameController.text,
|
|
pass:
|
|
databasePasswordController.text,
|
|
db: databaseNameController.text);
|
|
|
|
var responsePacket =
|
|
await settings.sendPacketToEndpoint(
|
|
APIEndpoint.Setup, packet)
|
|
as S2CSimpleReplyPacket;
|
|
|
|
if (responsePacket.done) {
|
|
settings.OpenSimSetupCompleted = true;
|
|
} else {
|
|
settings.OpenSimSetupCompleted = false;
|
|
}
|
|
|
|
didChangeDependencies();
|
|
|
|
setState(() {});
|
|
},
|
|
child: const Text("Submit"))
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
));
|
|
}
|
|
}
|