From 130baa88635471e3a8ad2f87774df9d6e20527f0 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Mon, 29 Apr 2024 12:05:48 -0400 Subject: [PATCH] - add mechanism for database upgrades - add ban reason - add api endpoints for banning - moderators can now list users/bots, and perform basic updates --- CollabVMAuthServer/CollabVMAuthServer.csproj | 1 + CollabVMAuthServer/Database.cs | 69 +++++++ CollabVMAuthServer/DatabaseUpdate.cs | 33 +++ CollabVMAuthServer/HTTP/AdminRoutes.cs | 189 +++++++++++++++++- CollabVMAuthServer/HTTP/DeveloperRoutes.cs | 8 +- .../HTTP/Payloads/BanUserPayload.cs | 9 + .../HTTP/Payloads/IPBanPayload.cs | 9 + .../HTTP/Responses/AdminUsersResponse.cs | 1 + .../HTTP/Responses/BanUserResponse.cs | 7 + .../HTTP/Responses/IPBanResponse.cs | 7 + .../HTTP/Responses/JoinResponse.cs | 2 + CollabVMAuthServer/HTTP/Routes.cs | 36 ++-- CollabVMAuthServer/Program.cs | 18 +- CollabVMAuthServer/User.cs | 1 + CollabVMAuthServer/Utilities.cs | 2 +- 15 files changed, 365 insertions(+), 27 deletions(-) create mode 100644 CollabVMAuthServer/DatabaseUpdate.cs create mode 100644 CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs create mode 100644 CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs create mode 100644 CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs create mode 100644 CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs diff --git a/CollabVMAuthServer/CollabVMAuthServer.csproj b/CollabVMAuthServer/CollabVMAuthServer.csproj index 740fd8a..219fbf3 100644 --- a/CollabVMAuthServer/CollabVMAuthServer.csproj +++ b/CollabVMAuthServer/CollabVMAuthServer.csproj @@ -8,6 +8,7 @@ false Computernewb.CollabVMAuthServer Computernewb Development Team + 1.1 diff --git a/CollabVMAuthServer/Database.cs b/CollabVMAuthServer/Database.cs index b14d99c..04b8e5d 100644 --- a/CollabVMAuthServer/Database.cs +++ b/CollabVMAuthServer/Database.cs @@ -37,6 +37,7 @@ public class Database password_reset_code CHAR(8) DEFAULT NULL, cvm_rank INT UNSIGNED NOT NULL DEFAULT 1, banned BOOLEAN NOT NULL DEFAULT 0, + ban_reason TEXT DEFAULT NULL, registration_ip VARBINARY(16) NOT NULL, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, developer BOOLEAN NOT NULL DEFAULT 0 @@ -76,6 +77,13 @@ public class Database ) """; await cmd.ExecuteNonQueryAsync(); + cmd.CommandText = """ + CREATE TABLE IF NOT EXISTS meta ( + setting VARCHAR(20) NOT NULL PRIMARY KEY, + val TEXT NOT NULL + ) + """; + await cmd.ExecuteNonQueryAsync(); } public async Task GetUser(string? username = null, string? email = null) @@ -110,6 +118,7 @@ public class Database PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"), Rank = (Rank)reader.GetUInt32("cvm_rank"), Banned = reader.GetBoolean("banned"), + BanReason = reader.IsDBNull("ban_reason") ? null : reader.GetString("ban_reason"), RegistrationIP = new IPAddress(reader.GetFieldValue("registration_ip")), Joined = reader.GetDateTime("created"), Developer = reader.GetBoolean("developer") @@ -363,6 +372,7 @@ public class Database PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"), Rank = (Rank)reader.GetUInt32("cvm_rank"), Banned = reader.GetBoolean("banned"), + BanReason = reader.IsDBNull("ban_reason") ? null : reader.GetString("ban_reason"), RegistrationIP = new IPAddress(reader.GetFieldValue("registration_ip")), Joined = reader.GetDateTime("created"), Developer = reader.GetBoolean("developer") @@ -478,4 +488,63 @@ public class Database Created = reader.GetDateTime("created") }; } + + public async Task GetDatabaseVersion() + { + await using var db = new MySqlConnection(connectionString); + await db.OpenAsync(); + await using var cmd = db.CreateCommand(); + // If `users` table doesn't exist, return -1. This is hacky but I don't know of a better way + cmd.CommandText = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'users'"; + if ((long)(await cmd.ExecuteScalarAsync() ?? 0) == 0) + return -1; + // If `meta` table doesn't exist, assume version 0 + cmd.CommandText = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'meta'"; + if ((long)(await cmd.ExecuteScalarAsync() ?? 0) == 0) + return 0; + cmd.CommandText = "SELECT val FROM meta WHERE setting = 'db_version'"; + await using var reader = await cmd.ExecuteReaderAsync(); + await reader.ReadAsync(); + return int.Parse(reader.GetString("val")); + } + + public async Task SetDatabaseVersion(int version) + { + await using var db = new MySqlConnection(connectionString); + await db.OpenAsync(); + await using var cmd = db.CreateCommand(); + cmd.CommandText = "INSERT INTO meta (setting, val) VALUES ('db_version', @version) ON DUPLICATE KEY UPDATE val = @version"; + cmd.Parameters.AddWithValue("@version", version.ToString()); + await cmd.ExecuteNonQueryAsync(); + } + + public async Task ExecuteNonQuery(string query) + { + await using var db = new MySqlConnection(connectionString); + await db.OpenAsync(); + await using var cmd = db.CreateCommand(); + cmd.CommandText = query; + await cmd.ExecuteNonQueryAsync(); + } + + public async Task SetBanned(string username, bool banned, string? reason) + { + await using var db = new MySqlConnection(connectionString); + await db.OpenAsync(); + await using var cmd = db.CreateCommand(); + cmd.CommandText = "UPDATE users SET banned = @banned, ban_reason = @reason WHERE username = @username"; + cmd.Parameters.AddWithValue("@banned", banned); + cmd.Parameters.AddWithValue("@reason", reason); + cmd.Parameters.AddWithValue("@username", username); + await cmd.ExecuteNonQueryAsync(); + } + + public async Task CountUsers() + { + await using var db = new MySqlConnection(connectionString); + await db.OpenAsync(); + await using var cmd = db.CreateCommand(); + cmd.CommandText = "SELECT COUNT(*) FROM users"; + return (long)await cmd.ExecuteScalarAsync(); + } } \ No newline at end of file diff --git a/CollabVMAuthServer/DatabaseUpdate.cs b/CollabVMAuthServer/DatabaseUpdate.cs new file mode 100644 index 0000000..8b60965 --- /dev/null +++ b/CollabVMAuthServer/DatabaseUpdate.cs @@ -0,0 +1,33 @@ +using System.Collections.ObjectModel; + +namespace Computernewb.CollabVMAuthServer; + +public static class DatabaseUpdate +{ + public const int CurrentVersion = 1; + + private static ReadOnlyDictionary> Updates = new Dictionary>() + { + { 1, async db => + { + // Update to version 1 + // Add ban_reason column to users table + await db.ExecuteNonQuery("ALTER TABLE users ADD COLUMN ban_reason TEXT DEFAULT NULL"); + }}, + }.AsReadOnly(); + + public async static Task Update(Database db) + { + var version = await db.GetDatabaseVersion(); + if (version == -1) throw new InvalidOperationException("Uninitialized database cannot be updated"); + if (version == CurrentVersion) return; + if (version > CurrentVersion) throw new InvalidOperationException("Database version is newer than the server supports"); + Utilities.Log(LogLevel.INFO, $"Updating database from version {version} to {CurrentVersion}"); + for (int i = version + 1; i <= CurrentVersion; i++) + { + if (!Updates.TryGetValue(i, out var update)) throw new InvalidOperationException($"No update available for version {i}"); + await update(db); + } + await db.SetDatabaseVersion(CurrentVersion); + } +} \ No newline at end of file diff --git a/CollabVMAuthServer/HTTP/AdminRoutes.cs b/CollabVMAuthServer/HTTP/AdminRoutes.cs index 021669d..f9b45df 100644 --- a/CollabVMAuthServer/HTTP/AdminRoutes.cs +++ b/CollabVMAuthServer/HTTP/AdminRoutes.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Text.Json; using Computernewb.CollabVMAuthServer.HTTP.Payloads; using Computernewb.CollabVMAuthServer.HTTP.Responses; @@ -11,6 +12,160 @@ public static class AdminRoutes app.MapPost("/api/v1/admin/users", (Delegate)HandleAdminUsers); app.MapPost("/api/v1/admin/updateuser", (Delegate)HandleAdminUpdateUser); app.MapPost("/api/v1/admin/updatebot", (Delegate)HandleAdminUpdateBot); + app.MapPost("/api/v1/admin/ban", (Delegate)HandleBanUser); + app.MapPost("/api/v1/admin/ipban", (Delegate)HandleIPBan); + } + + private static async Task HandleIPBan(HttpContext context) + { + // Check payload + if (context.Request.ContentType != "application/json") + { + context.Response.StatusCode = 400; + return Results.Json(new IPBanResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + IPBanPayload? payload; + try + { + payload = await context.Request.ReadFromJsonAsync(); + } + catch (JsonException ex) + { + Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}"); + context.Response.StatusCode = 400; + return Results.Json(new IPBanResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + if (payload == null || string.IsNullOrWhiteSpace(payload.session) || string.IsNullOrWhiteSpace(payload.ip) || (payload.banned && string.IsNullOrWhiteSpace(payload.reason)) || payload.banned == null || !IPAddress.TryParse(payload.ip, out var ip)) + { + context.Response.StatusCode = 400; + return Results.Json(new IPBanResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + // Check token + var session = await Program.Database.GetSession(payload.session); + if (session == null || Utilities.IsSessionExpired(session)) + { + context.Response.StatusCode = 400; + return Results.Json(new IPBanResponse + { + success = false, + error = "Invalid session" + }, Utilities.JsonSerializerOptions); + } + // Check rank + var user = await Program.Database.GetUser(session.Username) + ?? throw new Exception("Could not lookup user from session"); + if (user.Rank != Rank.Admin && user.Rank != Rank.Moderator) + { + context.Response.StatusCode = 403; + return Results.Json(new IPBanResponse + { + success = false, + error = "Insufficient permissions" + }, Utilities.JsonSerializerOptions); + } + // Set ban + if (payload.banned) + { + await Program.Database.BanIP(ip, payload.reason, user.Username); + } + else + { + await Program.Database.UnbanIP(ip); + } + return Results.Json(new IPBanResponse + { + success = true + }, Utilities.JsonSerializerOptions); + } + + private static async Task HandleBanUser(HttpContext context) + { + // Check payload + if (context.Request.ContentType != "application/json") + { + context.Response.StatusCode = 400; + return Results.Json(new BanUserResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + BanUserPayload? payload; + try + { + payload = await context.Request.ReadFromJsonAsync(); + } + catch (JsonException ex) + { + Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}"); + context.Response.StatusCode = 400; + return Results.Json(new BanUserResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + if (payload == null || string.IsNullOrWhiteSpace(payload.token) || string.IsNullOrWhiteSpace(payload.username) || (payload.banned && string.IsNullOrWhiteSpace(payload.reason)) || payload.banned == null) + { + context.Response.StatusCode = 400; + return Results.Json(new BanUserResponse + { + success = false, + error = "Invalid request body" + }, Utilities.JsonSerializerOptions); + } + // Check token + var session = await Program.Database.GetSession(payload.token); + if (session == null || Utilities.IsSessionExpired(session)) + { + context.Response.StatusCode = 400; + return Results.Json(new BanUserResponse + { + success = false, + error = "Invalid session" + }, Utilities.JsonSerializerOptions); + } + // Check rank + var user = await Program.Database.GetUser(session.Username) + ?? throw new Exception("Could not lookup user from session"); + if (user.Rank != Rank.Admin && user.Rank != Rank.Moderator) + { + context.Response.StatusCode = 403; + return Results.Json(new BanUserResponse + { + success = false, + error = "Insufficient permissions" + }, Utilities.JsonSerializerOptions); + } + // Check target user + var targetUser = await Program.Database.GetUser(payload.username); + if (targetUser == null) + { + context.Response.StatusCode = 400; + return Results.Json(new BanUserResponse + { + success = false, + error = "User not found" + }, Utilities.JsonSerializerOptions); + } + // Set ban + await Program.Database.SetBanned(targetUser.Username, payload.banned, payload.banned ? payload.reason : null); + return Results.Json(new BanUserResponse + { + success = true + }, Utilities.JsonSerializerOptions); } private static async Task HandleAdminUpdateBot(HttpContext context) @@ -63,7 +218,7 @@ public static class AdminRoutes // Check rank var user = await Program.Database.GetUser(session.Username) ?? throw new Exception("Could not lookup user from session"); - if (user.Rank != Rank.Admin) + if (user.Rank != Rank.Admin && user.Rank != Rank.Moderator) { context.Response.StatusCode = 403; return Results.Json(new AdminUsersResponse @@ -93,6 +248,25 @@ public static class AdminRoutes error = "No fields to update" }, Utilities.JsonSerializerOptions); } + // Moderators cannot promote bots to admin, and can only promote their own bots to moderator + else if ((Rank)payload.rank == Rank.Admin && user.Rank == Rank.Moderator) + { + context.Response.StatusCode = 403; + return Results.Json(new AdminUpdateBotResponse + { + success = false, + error = "Insufficient permissions" + }, Utilities.JsonSerializerOptions); + } + if (targetBot.Owner != user.Username && user.Rank == Rank.Moderator) + { + context.Response.StatusCode = 403; + return Results.Json(new AdminUpdateBotResponse + { + success = false, + error = "Insufficient permissions" + }, Utilities.JsonSerializerOptions); + } // Check rank int? rank = payload.rank; if (rank != null && rank < 1 || rank > 3) @@ -193,6 +367,16 @@ public static class AdminRoutes error = "Invalid rank" }, Utilities.JsonSerializerOptions); } + // Moderators cannot change ranks + if (user.Rank == Rank.Moderator && rank != null) + { + context.Response.StatusCode = 403; + return Results.Json(new AdminUpdateUserResponse + { + success = false, + error = "Insufficient permissions" + }, Utilities.JsonSerializerOptions); + } // Check developer bool? developer = payload.developer; // Update rank @@ -257,7 +441,7 @@ public static class AdminRoutes // Check rank var user = await Program.Database.GetUser(session.Username) ?? throw new Exception("Could not lookup user from session"); - if (user.Rank != Rank.Admin) + if (user.Rank != Rank.Admin && user.Rank != Rank.Moderator) { context.Response.StatusCode = 403; return Results.Json(new AdminUsersResponse @@ -293,6 +477,7 @@ public static class AdminRoutes email = user.Email, rank = (int)user.Rank, banned = user.Banned, + banReason = user.BanReason ?? "", dateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd"), dateJoined = user.Joined.ToString("yyyy-MM-dd HH:mm:ss"), registrationIp = user.RegistrationIP.ToString(), diff --git a/CollabVMAuthServer/HTTP/DeveloperRoutes.cs b/CollabVMAuthServer/HTTP/DeveloperRoutes.cs index 80406b0..ac85624 100644 --- a/CollabVMAuthServer/HTTP/DeveloperRoutes.cs +++ b/CollabVMAuthServer/HTTP/DeveloperRoutes.cs @@ -63,7 +63,7 @@ public static class DeveloperRoutes // Check developer status var user = await Program.Database.GetUser(session.Username) ?? throw new Exception("Unable to get user from session"); - if (!user.Developer && user.Rank != Rank.Admin) + if (!user.Developer && user.Rank != Rank.Admin && user.Rank != Rank.Moderator) { context.Response.StatusCode = 403; return Results.Json(new CreateBotResponse @@ -72,8 +72,8 @@ public static class DeveloperRoutes error = "You must be an approved developer to create and manage bots." }, Utilities.JsonSerializerOptions); } - // owner can only be specified by admins - if (payload.owner != null && user.Rank != Rank.Admin) + // owner can only be specified by admins and moderators + if (payload.owner != null && user.Rank != Rank.Admin && user.Rank != Rank.Moderator) { context.Response.StatusCode = 403; return Results.Json(new ListBotsResponse @@ -84,7 +84,7 @@ public static class DeveloperRoutes } // Get bots // If the user is not an admin, they can only see their own bots - var bots = (await Program.Database.ListBots(payload.owner ?? (user.Rank == Rank.Admin ? null : user.Username))).Select(bot => new ListBot + var bots = (await Program.Database.ListBots(payload.owner ?? ((user.Rank == Rank.Admin || user.Rank == Rank.Moderator) ? null : user.Username))).Select(bot => new ListBot { id = (int)bot.Id, username = bot.Username, diff --git a/CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs b/CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs new file mode 100644 index 0000000..cd7b729 --- /dev/null +++ b/CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs @@ -0,0 +1,9 @@ +namespace Computernewb.CollabVMAuthServer.HTTP.Payloads; + +public class BanUserPayload +{ + public string token { get; set; } + public string username { get; set; } + public bool banned { get; set; } + public string reason { get; set; } +} \ No newline at end of file diff --git a/CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs b/CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs new file mode 100644 index 0000000..872e76f --- /dev/null +++ b/CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs @@ -0,0 +1,9 @@ +namespace Computernewb.CollabVMAuthServer.HTTP.Payloads; + +public class IPBanPayload +{ + public string session { get; set; } + public string ip { get; set; } + public bool banned { get; set; } + public string reason { get; set; } +} \ No newline at end of file diff --git a/CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs b/CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs index adad16e..598e4ff 100644 --- a/CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs +++ b/CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs @@ -15,6 +15,7 @@ public class AdminUser public string email { get; set; } public int rank { get; set; } public bool banned { get; set; } + public string banReason { get; set; } public string dateOfBirth { get; set; } public string dateJoined { get; set; } public string registrationIp { get; set; } diff --git a/CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs b/CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs new file mode 100644 index 0000000..239d05e --- /dev/null +++ b/CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs @@ -0,0 +1,7 @@ +namespace Computernewb.CollabVMAuthServer.HTTP.Responses; + +public class BanUserResponse +{ + public bool success { get; set; } + public string? error { get; set; } +} \ No newline at end of file diff --git a/CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs b/CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs new file mode 100644 index 0000000..8f05ef5 --- /dev/null +++ b/CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs @@ -0,0 +1,7 @@ +namespace Computernewb.CollabVMAuthServer.HTTP.Responses; + +public class IPBanResponse +{ + public bool success { get; set; } + public string? error { get; set; } +} \ No newline at end of file diff --git a/CollabVMAuthServer/HTTP/Responses/JoinResponse.cs b/CollabVMAuthServer/HTTP/Responses/JoinResponse.cs index 8b7d3a6..5b37ef0 100644 --- a/CollabVMAuthServer/HTTP/Responses/JoinResponse.cs +++ b/CollabVMAuthServer/HTTP/Responses/JoinResponse.cs @@ -4,6 +4,8 @@ public class JoinResponse { public bool success { get; set; } public bool clientSuccess { get; set; } = false; + public bool? banned { get; set; } = null; + public string? banReason { get; set; } public string? error { get; set; } public string? username { get; set; } public Rank? rank { get; set; } diff --git a/CollabVMAuthServer/HTTP/Routes.cs b/CollabVMAuthServer/HTTP/Routes.cs index 23b633d..9661329 100644 --- a/CollabVMAuthServer/HTTP/Routes.cs +++ b/CollabVMAuthServer/HTTP/Routes.cs @@ -556,7 +556,9 @@ public static class Routes { success = true, clientSuccess = false, - error = "You are banned" + error = "Banned", + banned = true, + banReason = ban.Reason }, Utilities.JsonSerializerOptions); } // Check if session is valid @@ -592,7 +594,9 @@ public static class Routes { success = true, clientSuccess = false, - error = "You are banned", + banned = true, + error = "Banned", + banReason = user.BanReason }, Utilities.JsonSerializerOptions); } // Update session @@ -1026,33 +1030,29 @@ public static class Routes }); } // Create the account + string? token = null; if (Program.Config.Registration.EmailVerificationRequired) { var code = Program.Random.Next(10000000, 99999999).ToString(); await Program.Database.RegisterAccount(payload.username, payload.email, dob, payload.password, false, ip,code); await Program.Mailer.SendVerificationCode(payload.username, payload.email, code); - return Results.Json(new RegisterResponse - { - success = true, - verificationRequired = true, - email = payload.email, - username = payload.username - }, Utilities.JsonSerializerOptions); } else { await Program.Database.RegisterAccount(payload.username, payload.email, dob, payload.password, true, ip, null); - var token = Utilities.RandomString(32); + token = Utilities.RandomString(32); await Program.Database.CreateSession(payload.username, token, ip); - return Results.Json(new RegisterResponse - { - success = true, - verificationRequired = false, - email = payload.email, - username = payload.username, - sessionToken = token - }, Utilities.JsonSerializerOptions); } + // If this is the first user, make them an admin + if (await Program.Database.CountUsers() == 1) await Program.Database.UpdateUser(payload.username, newRank: (int)Rank.Admin); + return Results.Json(new RegisterResponse + { + success = true, + verificationRequired = Program.Config.Registration.EmailVerificationRequired, + email = payload.email, + username = payload.username, + sessionToken = token + }, Utilities.JsonSerializerOptions); } private static IResult HandleInfo(HttpContext context) diff --git a/CollabVMAuthServer/Program.cs b/CollabVMAuthServer/Program.cs index 95c00f7..91e4e04 100644 --- a/CollabVMAuthServer/Program.cs +++ b/CollabVMAuthServer/Program.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Reflection; using Computernewb.CollabVMAuthServer.HTTP; using Tomlet; @@ -14,7 +15,8 @@ public class Program public static readonly Random Random = new Random(); public static async Task Main(string[] args) { - Utilities.Log(LogLevel.INFO, "CollabVM Authentication Server starting up"); + var ver = Assembly.GetExecutingAssembly().GetName().Version; + Utilities.Log(LogLevel.INFO, $"CollabVM Authentication Server v{ver.Major}.{ver.Minor}.{ver.Revision} starting up"); // Read config.toml string configraw; try @@ -39,8 +41,20 @@ public class Program } // Initialize database Database = new Database(Config.MySQL); - await Database.Init(); + // Get version before initializing + int dbversion = await Database.GetDatabaseVersion(); Utilities.Log(LogLevel.INFO, "Connected to database"); + Utilities.Log(LogLevel.INFO, dbversion == -1 ? "Initializing tables..." : $"Database version: {dbversion}"); + await Database.Init(); + // If database was version 0, that should now be set, as versioning did not exist then + if (dbversion == 0) await Database.SetDatabaseVersion(0); + // If database was -1, that means it was just initialized and we should set it to the current version + if (dbversion == -1) await Database.SetDatabaseVersion(DatabaseUpdate.CurrentVersion); + // Perform any necessary database updates + await DatabaseUpdate.Update(Database); + var uc = await Database.CountUsers(); + Utilities.Log(LogLevel.INFO, $"{uc} users in database"); + if (uc == 0) Utilities.Log(LogLevel.WARN, "No users in database, first user will be promoted to admin"); // Create mailer if (!Config.SMTP.Enabled && Config.Registration.EmailVerificationRequired) { diff --git a/CollabVMAuthServer/User.cs b/CollabVMAuthServer/User.cs index 7fb2901..8cf599c 100644 --- a/CollabVMAuthServer/User.cs +++ b/CollabVMAuthServer/User.cs @@ -14,6 +14,7 @@ public class User public string? PasswordResetCode { get; set; } public Rank Rank { get; set; } public bool Banned { get; set; } + public string? BanReason { get; set; } public IPAddress RegistrationIP { get; set; } public DateTime Joined { get; set; } public bool Developer { get; set; } diff --git a/CollabVMAuthServer/Utilities.cs b/CollabVMAuthServer/Utilities.cs index 10f3f92..e3dfce4 100644 --- a/CollabVMAuthServer/Utilities.cs +++ b/CollabVMAuthServer/Utilities.cs @@ -63,7 +63,7 @@ public static class Utilities case LogLevel.WARN: case LogLevel.ERROR: case LogLevel.FATAL: - Console.Error.Write(logstr.ToString()); + Console.Error.WriteLine(logstr.ToString()); break; } }