diff --git a/AutoRegister/AutoRegister.csproj b/AutoRegister/AutoRegister.csproj index 4b7609e..4915f80 100644 --- a/AutoRegister/AutoRegister.csproj +++ b/AutoRegister/AutoRegister.csproj @@ -40,7 +40,7 @@ bin\Debug\BCrypt.Net.dll - ..\lib\OTAPI.dll + bin\Debug\OTAPI.dll @@ -50,10 +50,10 @@ - ..\lib\TerrariaServer.exe + bin\Debug\TerrariaServer.exe - ..\lib\TShockAPI.dll + bin\Debug\TShockAPI.dll diff --git a/AutoRegister/Plugin.cs b/AutoRegister/Plugin.cs index 09dbde1..753eb68 100644 --- a/AutoRegister/Plugin.cs +++ b/AutoRegister/Plugin.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; using Terraria; -using Terraria.Localization; using TerrariaApi.Server; using TShockAPI; using TShockAPI.DB; -using TShockAPI.Hooks; -using static TShockAPI.GetDataHandlers; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; namespace AutoRegister { @@ -19,6 +16,7 @@ namespace AutoRegister [ApiVersion(2, 1)] public class Plugin : TerrariaPlugin { + #region Plugin Info /// /// The name of the plugin. /// @@ -27,18 +25,21 @@ public class Plugin : TerrariaPlugin /// /// The version of the plugin in its current state. /// - public override Version Version => new Version(1, 0, 1); + public override Version Version => System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; /// /// The author(s) of the plugin. /// - public override string Author => "brian91292"; + public override string Author => "brian91292 & moisterrific"; /// /// A short, one-line, description of the plugin's purpose. /// - public override string Description => "A Tshock plugin to automatically register a new server-side character if one doesn't already exist for a user."; + public override string Description => "A TShock plugin to automatically register a user account for new players."; + #endregion + + #region Hooks and stuff /// /// The plugin's constructor /// Set your plugin's order (optional) and any other constructor logic here @@ -47,14 +48,6 @@ public Plugin(Main game) : base(game) { } - - public static void Log(string msg, - [CallerMemberName] string member = "", - [CallerLineNumber] int line = 0) - { - Console.WriteLine($"AutoRegister::{member}({line}): {msg}"); - } - /// /// Performs plugin initialization logic. /// Add your hooks, config file read/writes, etc here @@ -64,25 +57,53 @@ public override void Initialize() ServerApi.Hooks.ServerJoin.Register(this, OnServerJoin); ServerApi.Hooks.NetGreetPlayer.Register(this, OnGreetPlayer, 420); } + #endregion + + private readonly Dictionary tmpPasswords = new Dictionary(); + + private static readonly string result = GenerateRandomAlphanumericString(); - private Dictionary tmpPasswords = new Dictionary(); /// /// Tell the player their password if the account was newly generated /// /// - void OnGreetPlayer(GreetPlayerEventArgs args) + async void OnGreetPlayer(GreetPlayerEventArgs args) { + var tsConfig = TShock.Config.Settings; var player = TShock.Players[args.Who]; - - if (tmpPasswords.TryGetValue(player.Name + player.UUID + player.IP, out string newPass)) - { + string cmd = TShock.Config.Settings.CommandSpecifier; + string red = TShockAPI.Utils.RedHighlight; + string green = TShockAPI.Utils.GreenHighlight; + string blue = TShockAPI.Utils.BoldHighlight; + + if (tsConfig.DisableUUIDLogin && !tsConfig.DisableLoginBeforeJoin) + return; + + // Need to put a slight delay otherwise the player might miss these important messages + // Because the messages always come before TShock MOTD + await Task.Delay(1000); + if (tmpPasswords.TryGetValue(result, out string password)) + { try { - player.SendSuccessMessage($"New server-side character created successfully! Your password is \"{newPass}\"."); - player.SendSuccessMessage($"Contact an admin if you lose access to this account, or forget your password."); + player.SendMessage($"Your account \"{player.Name.Color(blue)}\" has been auto-registered.", Color.White); + player.SendMessage($"Your randomly generated password is {password.Color(green)}", Color.White); + if (TShock.Config.Settings.DisableUUIDLogin) + player.SendMessage($"Please sign in using {cmd}login {password.Color(green)}", Color.White); + player.SendMessage($"You can change this at any time by using {cmd}password {password.Color(green)} \"{"new password".Color(red)}\"", Color.White); } - catch { } - tmpPasswords.Remove(player.Name + player.UUID + player.IP); + catch + { + player.SendErrorMessage("Failed to retrieve your randomly generated password, please contact your server administrator."); + TShock.Log.ConsoleError("AutoRegister returned an error."); + } + tmpPasswords.Remove(result); + } + else if (!player.IsLoggedIn) + { + player.SendErrorMessage($"Your account \"{player.Name}\" could not be auto-registered!"); + player.SendErrorMessage("This name has already been registered by another player."); + player.SendErrorMessage("Please try again using a different name."); } } @@ -92,32 +113,67 @@ void OnGreetPlayer(GreetPlayerEventArgs args) /// void OnServerJoin(JoinEventArgs args) { - if (TShock.ServerSideCharacterConfig.Enabled) + var tsConfig = TShock.Config.Settings; + + // Problem: if DisableUUIDLogin = true AND DisableLoginBeforeJoin = false + // The player will be greeted with a screen asking for their account password before they can enter the server + // so they won't ever see their randomly generated password in the chat + // bruh + if (tsConfig.DisableUUIDLogin && !tsConfig.DisableLoginBeforeJoin) + { + TShock.Log.ConsoleError("AutoRegister will not work when DisableUUIDLogin is true AND DisableLoginBeforeJoin is false!"); + return; + } + + // Whether this plugin should be disabled if the server doesn't require login is up for debate + // but for now I'll leave it like this + if (!tsConfig.RequireLogin) + { + TShock.Log.ConsoleError("AutoRegister will not work when RequireLogin is set to false via config!"); + return; + } + + if (tsConfig.RequireLogin || Main.ServerSideCharacter) { var player = TShock.Players[args.Who]; - // Get the user using a combo of their UUID/name, as this is what's required for uuid login to function it seems - var users = TShock.Users.GetUsers().Where(u => u.UUID == player.UUID && u.Name == player.Name); - if (users.Count() == 0) + if (TShock.UserAccounts.GetUserAccountByName(player.Name) == null && player.Name != TSServerPlayer.AccountName) { - Log($"Creating new user for {player.Name}..."); - - // If the user didn't exist, generate a password for them then create a new user based on their uuid/username - tmpPasswords[player.Name + player.UUID + player.IP] = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 10); - TShock.Users.AddUser(new User( + tmpPasswords[result] = + Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 10).Replace('l', 'L') + .Replace('1', '7').Replace('I', 'i').Replace('O', 'o').Replace('0', 'o'); + TShock.UserAccounts.AddUserAccount(new UserAccount( player.Name, - BCrypt.Net.BCrypt.HashPassword(tmpPasswords[player.Name + player.UUID + player.IP].Trim()), + BCrypt.Net.BCrypt.HashPassword(tmpPasswords[result].Trim(), tsConfig.BCryptWorkFactor), player.UUID, - TShock.Config.DefaultRegistrationGroupName, + tsConfig.DefaultRegistrationGroupName, DateTime.UtcNow.ToString("s"), DateTime.UtcNow.ToString("s"), "")); - Log("Success!"); + TShock.Log.ConsoleInfo($"Auto-registered an account for \"{player.Name}\" ({player.IP})"); } + else + TShock.Log.ConsoleInfo($"Unable to auto-register \"{player.Name}\" ({player.IP}) because an account with this name already exists."); } } + // To-Do: switch to using a cryptographically secure pseudorandom number instead of this + /// + /// Generates a random alphanumeric string. + /// + /// The desired length of the string + /// The string which has been generated + public static string GenerateRandomAlphanumericString(int length = 10) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + var random = new Random(); + var randomString = new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + return randomString; + } + /// /// Performs plugin cleanup logic /// Remove your hooks and perform general cleanup here diff --git a/AutoRegister/Properties/AssemblyInfo.cs b/AutoRegister/Properties/AssemblyInfo.cs index a5f62b7..73b5ca9 100644 --- a/AutoRegister/Properties/AssemblyInfo.cs +++ b/AutoRegister/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("AutoRegister")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("brian91292")] +[assembly: AssemblyCompany("brian91292 & moisterrific")] [assembly: AssemblyProduct("AutoRegister")] -[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.1.0")] -[assembly: AssemblyFileVersion("1.0.1.0")] +[assembly: AssemblyVersion("1.3.2.0")] +[assembly: AssemblyFileVersion("1.3.2.0")] diff --git a/AutoRegister/references/BCrypt.Net.dll b/AutoRegister/references/BCrypt.Net.dll new file mode 100644 index 0000000..c0eed4a Binary files /dev/null and b/AutoRegister/references/BCrypt.Net.dll differ diff --git a/AutoRegister/references/OTAPI.dll b/AutoRegister/references/OTAPI.dll new file mode 100644 index 0000000..275526d Binary files /dev/null and b/AutoRegister/references/OTAPI.dll differ diff --git a/AutoRegister/references/TShockAPI.dll b/AutoRegister/references/TShockAPI.dll new file mode 100644 index 0000000..d632637 Binary files /dev/null and b/AutoRegister/references/TShockAPI.dll differ diff --git a/AutoRegister/references/TerrariaServer.exe b/AutoRegister/references/TerrariaServer.exe new file mode 100644 index 0000000..233d9cc Binary files /dev/null and b/AutoRegister/references/TerrariaServer.exe differ diff --git a/README.md b/README.md index 769db6e..3e0b76a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ -# Mod Info -AutoRegister is a TShock plugin created to automatically register new users on SSC enabled servers. +# Plugin Info +AutoRegister is a plugin created to automatically register new players on TShock servers requiring login. -# How it works -When a new user joins a server AutoRegister checks to see if there is already a user present with their name/uuid. If there isn't, a new user is automatically created for them. The password which is generated is then posted in chat for them to either save, or change. \ No newline at end of file +## Overview +### How it works +When a new user joins a server, AutoRegister checks to see if there is an existing user with their name/UUID in the database. If there isn't, a new user account is automatically created for the new player. The randomly generated password is then posted in the chat for them to save or change. + +### Features +- Enables seamless onboarding of new players on login required servers. No more players getting confused and bombarding the host with "Why am I stoned/frozen/webbed/stuck?" questions. +- Automatically register a new account with a randomly generated password if an account by the player's name does not exist in the database. +- Users will be notified in the chat of their newly AutoRegistered account and randomly generated password, which they can change later. +- If an account by the same name as the new player already exist in the database, he/she will be notified in the chat to try a different username. +- Automatic registration success or failure is present in the same way as TShock for improved clarity and cohesion. +- Writes to server console and logs if a new user was successfully registered. +- Compatible with PC TShock 4.5.3 + +## Installation Guide +1. Copy and paste AutoRegister.dll into your ServerPlugins folder. That's it. +2. If the server is running, restart it. Otherwise start the server. + +## Source +[AutoRegister](https://tshock.co/xf/index.php?resources/autoregister.234/)