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;
}
}