Begin to add a new ACL system
This commit is contained in:
parent
03fef9863f
commit
f7c71230f0
9 changed files with 293 additions and 44 deletions
|
@ -5,6 +5,7 @@ import 'package:libac_dart/utils/IOTools.dart';
|
||||||
import 'package:servermanager/game.dart';
|
import 'package:servermanager/game.dart';
|
||||||
import 'package:servermanager/packets/ClientPackets.dart';
|
import 'package:servermanager/packets/ClientPackets.dart';
|
||||||
import 'package:servermanager/structs/SessionData.dart';
|
import 'package:servermanager/structs/SessionData.dart';
|
||||||
|
import 'package:servermanager/structs/credentials.dart';
|
||||||
import 'package:servermanager/structs/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
@ -35,6 +36,9 @@ void main() async {
|
||||||
settings.Write();
|
settings.Write();
|
||||||
print("Wrote settings.dat");
|
print("Wrote settings.dat");
|
||||||
|
|
||||||
|
settings.superuser = User.make(settings.serverLoginCreds.username,
|
||||||
|
settings.serverLoginCreds.password, UserLevel.Super_User);
|
||||||
|
|
||||||
print("Initializing SteamCMD");
|
print("Initializing SteamCMD");
|
||||||
await settings.initializeSteamCmd();
|
await settings.initializeSteamCmd();
|
||||||
print("Initialized Steamcmd");
|
print("Initialized Steamcmd");
|
||||||
|
|
|
@ -80,12 +80,21 @@ class ServerPage extends StatelessWidget {
|
||||||
loginResponse.handleClientPacket();
|
loginResponse.handleClientPacket();
|
||||||
|
|
||||||
if (loginResponse.valid) {
|
if (loginResponse.valid) {
|
||||||
S2CResponse settingsData =
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
await settings.client!.send(C2SRequestSettingsPacket(), true);
|
content:
|
||||||
C2SRequestSettingsPacket settingsBack =
|
Text("Login Success - Downloading remote Settings")));
|
||||||
C2SRequestSettingsPacket();
|
while (true) {
|
||||||
settingsBack.decodeTag(settingsData.contents);
|
try {
|
||||||
settingsBack.handleClientPacket();
|
await Future.delayed(Duration(seconds: 5));
|
||||||
|
S2CResponse settingsData = await settings.client!
|
||||||
|
.send(C2SRequestSettingsPacket(), true);
|
||||||
|
C2SRequestSettingsPacket settingsBack =
|
||||||
|
C2SRequestSettingsPacket();
|
||||||
|
settingsBack.decodeTag(settingsData.contents);
|
||||||
|
settingsBack.handleClientPacket();
|
||||||
|
break;
|
||||||
|
} catch (E) {}
|
||||||
|
}
|
||||||
|
|
||||||
Navigator.pushNamed(context, "/home");
|
Navigator.pushNamed(context, "/home");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:libac_dart/utils/uuid/NbtUUID.dart';
|
||||||
import 'package:libac_dart/utils/uuid/UUID.dart';
|
import 'package:libac_dart/utils/uuid/UUID.dart';
|
||||||
import 'package:servermanager/statemachine.dart';
|
import 'package:servermanager/statemachine.dart';
|
||||||
import 'package:servermanager/structs/SessionData.dart';
|
import 'package:servermanager/structs/SessionData.dart';
|
||||||
|
import 'package:servermanager/structs/credentials.dart';
|
||||||
import 'package:servermanager/structs/discordHookHelper.dart';
|
import 'package:servermanager/structs/discordHookHelper.dart';
|
||||||
import 'package:servermanager/structs/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
|
@ -104,17 +105,40 @@ class C2SLoginPacket implements IPacket {
|
||||||
|
|
||||||
// Attempt to log in.
|
// Attempt to log in.
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
if (settings.serverLoginCreds.username == username &&
|
|
||||||
Hashing.sha256Hash(settings.serverLoginCreds.password) == password) {
|
if (settings.superuser!.login(username, password)) {
|
||||||
settings.remoteLoginToken = UUID.generate(4);
|
settings.remoteLoginToken = UUID.generate(4);
|
||||||
loginReply.valid = true;
|
loginReply.valid = true;
|
||||||
loginReply.token = settings.remoteLoginToken;
|
loginReply.token = settings.remoteLoginToken;
|
||||||
|
|
||||||
|
settings.superuser!.sendDiscordActionLog("Login Success");
|
||||||
|
|
||||||
|
settings.loggedInUser = settings.superuser;
|
||||||
} else {
|
} else {
|
||||||
//print(
|
|
||||||
// "Login failure\n${settings.serverLoginCreds.username}:${username}\n${Hashing.sha256Hash(settings.serverLoginCreds.password)}:${password}");
|
|
||||||
loginReply.valid = false;
|
loginReply.valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!loginReply.valid && settings.superuser!.name != username) {
|
||||||
|
// Check for a lower level user
|
||||||
|
if (settings.inst!.admins.any((T) => T.name == username)) {
|
||||||
|
User theUser =
|
||||||
|
settings.inst!.admins.firstWhere((T) => T.name == username);
|
||||||
|
if (theUser.login(username, password)) {
|
||||||
|
settings.remoteLoginToken = UUID.generate(4);
|
||||||
|
loginReply.valid = true;
|
||||||
|
loginReply.token = settings.remoteLoginToken;
|
||||||
|
|
||||||
|
theUser.sendDiscordActionLog("Login Success");
|
||||||
|
|
||||||
|
settings.loggedInUser = theUser;
|
||||||
|
} else {
|
||||||
|
loginReply.valid = false;
|
||||||
|
|
||||||
|
theUser.sendDiscordActionLog("Login Failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.contents = loginReply.encodeTag().asCompoundTag();
|
response.contents = loginReply.encodeTag().asCompoundTag();
|
||||||
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
|
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
|
||||||
}
|
}
|
||||||
|
@ -414,6 +438,9 @@ class C2SRequestCreateBackup implements IPacket {
|
||||||
PathHelper pth = PathHelper(pth: settings.getWorldSnapshotFolder())
|
PathHelper pth = PathHelper(pth: settings.getWorldSnapshotFolder())
|
||||||
.resolve(destinationFile);
|
.resolve(destinationFile);
|
||||||
world.copy(pth.build());
|
world.copy(pth.build());
|
||||||
|
|
||||||
|
settings.loggedInUser!
|
||||||
|
.sendDiscordActionLog("Created a new backup named ${fileName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return PacketResponse.nil;
|
return PacketResponse.nil;
|
||||||
|
@ -473,7 +500,8 @@ class C2SRequestSnapshotList implements IPacket {
|
||||||
// add file name without db extension to the list
|
// add file name without db extension to the list
|
||||||
|
|
||||||
String trimmedFileName = str.trim().substring(0, str.trim().length - 3);
|
String trimmedFileName = str.trim().substring(0, str.trim().length - 3);
|
||||||
strippedFiles.add(trimmedFileName);
|
strippedFiles
|
||||||
|
.add(trimmedFileName.substring(trimmedFileName.lastIndexOf("/")));
|
||||||
} else {
|
} else {
|
||||||
strippedFiles.add(str);
|
strippedFiles.add(str);
|
||||||
}
|
}
|
||||||
|
@ -606,6 +634,9 @@ class C2SRequestSnapshotDeletion implements IPacket {
|
||||||
.resolve(correctedName);
|
.resolve(correctedName);
|
||||||
ph.deleteFile();
|
ph.deleteFile();
|
||||||
|
|
||||||
|
settings.loggedInUser!.sendDiscordActionLog(
|
||||||
|
"Requested snapshot deletion of backup named: ${snapshotName}");
|
||||||
|
|
||||||
return PacketResponse.nil;
|
return PacketResponse.nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,12 +689,17 @@ class C2SRequestWorldRestore implements IPacket {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PacketResponse> handleServerPacket() async {
|
Future<PacketResponse> handleServerPacket() async {
|
||||||
|
Settings settings = Settings();
|
||||||
|
|
||||||
SessionData.isWorldRestore = true;
|
SessionData.isWorldRestore = true;
|
||||||
SessionData.snapshotToRestore = snapshot;
|
SessionData.snapshotToRestore = snapshot;
|
||||||
SessionData.shutdownMessage = "A backup restore has been requested";
|
SessionData.shutdownMessage = "A backup restore has been requested";
|
||||||
SessionData.timer.apply(30);
|
SessionData.timer.apply(30);
|
||||||
SessionData.CURRENT_INTERVAL = WarnIntervals.NONE;
|
SessionData.CURRENT_INTERVAL = WarnIntervals.NONE;
|
||||||
|
|
||||||
|
settings.loggedInUser!.sendDiscordActionLog(
|
||||||
|
"Requested world restore, and initiated a immediate restart. World restored: ${snapshot}");
|
||||||
|
|
||||||
return PacketResponse.nil;
|
return PacketResponse.nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ class CredentialsPrompt extends State<CredentialsPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("Conan Exiles Server Manager - Credentials"),
|
title: Text("Conan Exiles Server Manager - Credentials (Super User)"),
|
||||||
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
||||||
),
|
),
|
||||||
floatingActionButton: ElevatedButton(
|
floatingActionButton: ElevatedButton(
|
||||||
|
@ -98,11 +98,3 @@ class CredentialsPrompt extends State<CredentialsPage> {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class User {
|
|
||||||
String name;
|
|
||||||
String passwordHash;
|
|
||||||
String passwordSalt;
|
|
||||||
|
|
||||||
User({required this.name, required this.passwordHash, required this.passwordSalt});
|
|
||||||
}
|
|
|
@ -77,6 +77,10 @@ class SnapshotsState extends State<SnapshotsPage> {
|
||||||
|
|
||||||
if (SessionData.IE_SNAPSHOTS.isNotEmpty) {
|
if (SessionData.IE_SNAPSHOTS.isNotEmpty) {
|
||||||
// Show the list!
|
// Show the list!
|
||||||
|
await Navigator.pushNamed(
|
||||||
|
context, "/server/snapshots/restore");
|
||||||
|
|
||||||
|
SessionData.IE_SNAPSHOTS.clear();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -108,36 +112,92 @@ class SnapshotListState extends State<SnapshotListPage> {
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
child: SingleChildScrollView(
|
child: ListView.builder(
|
||||||
child: ListView.builder(itemBuilder: (ctx, index) {
|
itemBuilder: (ctx, index) {
|
||||||
String filename = SessionData.IE_SNAPSHOTS[index];
|
String filename = SessionData.IE_SNAPSHOTS[index];
|
||||||
return ListTile(
|
|
||||||
title: Text(filename),
|
return ListTile(
|
||||||
leading: Icon(Icons.photo),
|
title: Text(filename),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"No information is known about this snapshot at this time"),
|
"No information is known about this snapshot at this time"),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// Send restore packet
|
// Send restore packet
|
||||||
|
var result = await showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (builder) {
|
||||||
|
return CupertinoAlertDialog(
|
||||||
|
title: Text("DANGER"),
|
||||||
|
content: Text(
|
||||||
|
"This action cannot be reversed, are you sure you want to restore $filename?"),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: Text("YES")),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text("ABORT"))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result == null) return;
|
||||||
|
C2SRequestWorldRestore rwr = C2SRequestWorldRestore();
|
||||||
|
rwr.snapshot = filename;
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.restore)),
|
icon: Icon(Icons.restore),
|
||||||
IconButton(
|
),
|
||||||
onPressed: () {
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
// Send deletion packet
|
// Send deletion packet
|
||||||
|
var result = await showCupertinoDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (builder) {
|
||||||
|
return AlertDialog(
|
||||||
|
icon: Icon(Icons.delete_forever),
|
||||||
|
title: Text("DANGER"),
|
||||||
|
content: Text(
|
||||||
|
"This action is not reversible. Are you sure?"),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: Text("Yes")),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, null);
|
||||||
|
},
|
||||||
|
child: Text("ABORT"))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
C2SRequestSnapshotDeletion rsd =
|
C2SRequestSnapshotDeletion rsd =
|
||||||
C2SRequestSnapshotDeletion();
|
C2SRequestSnapshotDeletion();
|
||||||
rsd.snapshotName = filename;
|
rsd.snapshotName = filename;
|
||||||
|
|
||||||
settings.client!.send(rsd, true);
|
settings.client!.send(rsd, true);
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.delete)),
|
icon: Icon(Icons.delete),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
})),
|
onTap: () {
|
||||||
|
// Handle tap on the list tile if needed
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: SessionData.IE_SNAPSHOTS.length,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import 'package:libac_dart/nbt/impl/CompoundTag.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/nbt/impl/StringTag.dart';
|
||||||
|
import 'package:libac_dart/utils/Hashing.dart';
|
||||||
|
import 'package:servermanager/structs/discordHookHelper.dart';
|
||||||
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
class Credentials {
|
class Credentials {
|
||||||
String username;
|
String username;
|
||||||
|
@ -28,3 +32,121 @@ class Credentials {
|
||||||
static const TAG_USERNAME = "username";
|
static const TAG_USERNAME = "username";
|
||||||
static const TAG_PASSWORD = "password";
|
static const TAG_PASSWORD = "password";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
String name;
|
||||||
|
String passwordHash;
|
||||||
|
String passwordSalt;
|
||||||
|
UserLevel permissions;
|
||||||
|
final String userHash;
|
||||||
|
|
||||||
|
bool login(String username, String passwordHash) {
|
||||||
|
if (userHash != generateValidityCheck())
|
||||||
|
return false; // User will be thrown away next time the data is reloaded
|
||||||
|
|
||||||
|
if (name == username) {
|
||||||
|
if (Hashing.sha256Hash("${passwordSalt}:${passwordHash}") ==
|
||||||
|
this.passwordHash) {
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendDiscordActionLog(String actionMessage) async {
|
||||||
|
Settings settings = Settings();
|
||||||
|
|
||||||
|
DiscordHookHelper.sendWebHook(
|
||||||
|
settings.inst!.discord,
|
||||||
|
DiscordHookProps.ALERT,
|
||||||
|
"User Action Alert",
|
||||||
|
"${this}: ${actionMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
User(
|
||||||
|
{required this.name,
|
||||||
|
required this.passwordHash,
|
||||||
|
required this.passwordSalt,
|
||||||
|
required this.permissions,
|
||||||
|
required this.userHash});
|
||||||
|
|
||||||
|
CompoundTag serialize() {
|
||||||
|
CompoundTag ct = CompoundTag();
|
||||||
|
ct.put(TAG_NAME, StringTag.valueOf(name));
|
||||||
|
ct.put(TAG_HASH, StringTag.valueOf(passwordHash));
|
||||||
|
ct.put(TAG_SALT, StringTag.valueOf(passwordSalt));
|
||||||
|
ct.put(TAG_PERMS, IntTag.valueOf(permissions.ord()));
|
||||||
|
ct.put(TAG_SEC_CODE, StringTag.valueOf(userHash));
|
||||||
|
|
||||||
|
return ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
static User deserialize(CompoundTag ct) {
|
||||||
|
return User(
|
||||||
|
name: ct.get(TAG_NAME)!.asString(),
|
||||||
|
passwordHash: ct.get(TAG_HASH)!.asString(),
|
||||||
|
passwordSalt: ct.get(TAG_SALT)!.asString(),
|
||||||
|
permissions: UserLevel.of(ct.get(TAG_PERMS)!.asInt()),
|
||||||
|
userHash: ct.get(TAG_SEC_CODE)!.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
factory User.make(String name, String password, UserLevel level) {
|
||||||
|
String salt = Hashing.sha256Hash(
|
||||||
|
"${Hashing.md5Hash("${Hashing.sha256Hash("${DateTime.now().millisecondsSinceEpoch}")}")}");
|
||||||
|
String hash = Hashing.sha256Hash("${salt}:${Hashing.sha256Hash(password)}");
|
||||||
|
String validityCode = generateValidityCode(name, hash, salt, level);
|
||||||
|
|
||||||
|
return User(
|
||||||
|
name: name,
|
||||||
|
passwordHash: hash,
|
||||||
|
passwordSalt: salt,
|
||||||
|
permissions: level,
|
||||||
|
userHash: validityCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String generateValidityCheck() {
|
||||||
|
return Hashing.sha256Hash(
|
||||||
|
"${name}:${passwordHash}:${passwordSalt}:${permissions.ord()}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String generateValidityCode(String name, String passwordHash,
|
||||||
|
String passwordSalt, UserLevel permissions) {
|
||||||
|
return Hashing.sha256Hash(
|
||||||
|
"${name}:${passwordHash}:${passwordSalt}:${permissions.ord()}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TAG_NAME = "name";
|
||||||
|
static const TAG_HASH = "hash";
|
||||||
|
static const TAG_SALT = "salt";
|
||||||
|
static const TAG_PERMS = "perms";
|
||||||
|
static const TAG_SEC_CODE = "validity";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "${permissions.name}: ${name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserLevel {
|
||||||
|
Super_User(0),
|
||||||
|
Administrator(1),
|
||||||
|
Operator(2),
|
||||||
|
None(99);
|
||||||
|
|
||||||
|
final int _id;
|
||||||
|
const UserLevel(this._id);
|
||||||
|
int ord() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static UserLevel of(int ord) {
|
||||||
|
for (var lvl in values) {
|
||||||
|
if (lvl.ord() == ord) {
|
||||||
|
return lvl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserLevel.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ class Settings {
|
||||||
UUID remoteLoginToken = UUID.ZERO;
|
UUID remoteLoginToken = UUID.ZERO;
|
||||||
|
|
||||||
PacketClient? client;
|
PacketClient? client;
|
||||||
|
User? superuser;
|
||||||
|
|
||||||
|
User? loggedInUser;
|
||||||
|
|
||||||
StateMachine subsys = StateMachine();
|
StateMachine subsys = StateMachine();
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import 'package:servermanager/structs/serversettings.dart';
|
||||||
|
|
||||||
class SettingsEntry {
|
class SettingsEntry {
|
||||||
List<Mod> mods = [];
|
List<Mod> mods = [];
|
||||||
|
List<User> admins = [];
|
||||||
|
|
||||||
DiscordHookProps discord =
|
DiscordHookProps discord =
|
||||||
DiscordHookProps(url: "", serverName: "", enabled: false);
|
DiscordHookProps(url: "", serverName: "", enabled: false);
|
||||||
Credentials? steam_creds;
|
Credentials? steam_creds;
|
||||||
|
@ -41,6 +43,20 @@ class SettingsEntry {
|
||||||
tag.get(DiscordHookProps.TAG_NAME)!.asCompoundTag());
|
tag.get(DiscordHookProps.TAG_NAME)!.asCompoundTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tag.containsKey("admins")) {
|
||||||
|
ListTag adminUsers = tag.get("admins")! as ListTag;
|
||||||
|
for (int i = 0; i < adminUsers.size(); i++) {
|
||||||
|
CompoundTag entry = adminUsers.get(i).asCompoundTag();
|
||||||
|
User loadedUser = User.deserialize(entry);
|
||||||
|
if (loadedUser.userHash == loadedUser.generateValidityCheck()) {
|
||||||
|
st.admins.add(loadedUser);
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
"/!\\ FATAL /!\\\n\n${loadedUser} failed to pass the validity check and has been tampered with");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
st.mods.clear();
|
st.mods.clear();
|
||||||
ListTag lMods = tag.get("mods") as ListTag;
|
ListTag lMods = tag.get("mods") as ListTag;
|
||||||
for (Tag tag in lMods.value) {
|
for (Tag tag in lMods.value) {
|
||||||
|
@ -65,6 +81,13 @@ class SettingsEntry {
|
||||||
|
|
||||||
tag.put(DiscordHookProps.TAG_NAME, discord.serialize());
|
tag.put(DiscordHookProps.TAG_NAME, discord.serialize());
|
||||||
|
|
||||||
|
ListTag adminUsers = ListTag();
|
||||||
|
for (User usr in admins) {
|
||||||
|
adminUsers.add(usr.serialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.put("admins", adminUsers);
|
||||||
|
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.1.0+32
|
version: 1.1.0+33
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.4 <4.0.0'
|
sdk: '>=3.1.4 <4.0.0'
|
||||||
|
@ -40,7 +40,7 @@ dependencies:
|
||||||
crypto:
|
crypto:
|
||||||
libac_dart:
|
libac_dart:
|
||||||
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
|
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
|
||||||
version: 1.0.33
|
version: 1.0.34
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue