forked from collabvm/CollabVMAuthServer
- add mechanism for database upgrades
- add ban reason - add api endpoints for banning - moderators can now list users/bots, and perform basic updates
This commit is contained in:
parent
7e7d9f6e92
commit
130baa8863
15 changed files with 365 additions and 27 deletions
|
@ -8,6 +8,7 @@
|
||||||
<PublishAot>false</PublishAot>
|
<PublishAot>false</PublishAot>
|
||||||
<RootNamespace>Computernewb.CollabVMAuthServer</RootNamespace>
|
<RootNamespace>Computernewb.CollabVMAuthServer</RootNamespace>
|
||||||
<Company>Computernewb Development Team</Company>
|
<Company>Computernewb Development Team</Company>
|
||||||
|
<AssemblyVersion>1.1</AssemblyVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -37,6 +37,7 @@ public class Database
|
||||||
password_reset_code CHAR(8) DEFAULT NULL,
|
password_reset_code CHAR(8) DEFAULT NULL,
|
||||||
cvm_rank INT UNSIGNED NOT NULL DEFAULT 1,
|
cvm_rank INT UNSIGNED NOT NULL DEFAULT 1,
|
||||||
banned BOOLEAN NOT NULL DEFAULT 0,
|
banned BOOLEAN NOT NULL DEFAULT 0,
|
||||||
|
ban_reason TEXT DEFAULT NULL,
|
||||||
registration_ip VARBINARY(16) NOT NULL,
|
registration_ip VARBINARY(16) NOT NULL,
|
||||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
developer BOOLEAN NOT NULL DEFAULT 0
|
developer BOOLEAN NOT NULL DEFAULT 0
|
||||||
|
@ -76,6 +77,13 @@ public class Database
|
||||||
)
|
)
|
||||||
""";
|
""";
|
||||||
await cmd.ExecuteNonQueryAsync();
|
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<User?> GetUser(string? username = null, string? email = null)
|
public async Task<User?> 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"),
|
PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"),
|
||||||
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
||||||
Banned = reader.GetBoolean("banned"),
|
Banned = reader.GetBoolean("banned"),
|
||||||
|
BanReason = reader.IsDBNull("ban_reason") ? null : reader.GetString("ban_reason"),
|
||||||
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
||||||
Joined = reader.GetDateTime("created"),
|
Joined = reader.GetDateTime("created"),
|
||||||
Developer = reader.GetBoolean("developer")
|
Developer = reader.GetBoolean("developer")
|
||||||
|
@ -363,6 +372,7 @@ public class Database
|
||||||
PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"),
|
PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"),
|
||||||
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
||||||
Banned = reader.GetBoolean("banned"),
|
Banned = reader.GetBoolean("banned"),
|
||||||
|
BanReason = reader.IsDBNull("ban_reason") ? null : reader.GetString("ban_reason"),
|
||||||
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
||||||
Joined = reader.GetDateTime("created"),
|
Joined = reader.GetDateTime("created"),
|
||||||
Developer = reader.GetBoolean("developer")
|
Developer = reader.GetBoolean("developer")
|
||||||
|
@ -478,4 +488,63 @@ public class Database
|
||||||
Created = reader.GetDateTime("created")
|
Created = reader.GetDateTime("created")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> 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<long> 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();
|
||||||
|
}
|
||||||
}
|
}
|
33
CollabVMAuthServer/DatabaseUpdate.cs
Normal file
33
CollabVMAuthServer/DatabaseUpdate.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Computernewb.CollabVMAuthServer;
|
||||||
|
|
||||||
|
public static class DatabaseUpdate
|
||||||
|
{
|
||||||
|
public const int CurrentVersion = 1;
|
||||||
|
|
||||||
|
private static ReadOnlyDictionary<int, Func<Database, Task>> Updates = new Dictionary<int, Func<Database, Task>>()
|
||||||
|
{
|
||||||
|
{ 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Net;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
using Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||||
using Computernewb.CollabVMAuthServer.HTTP.Responses;
|
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/users", (Delegate)HandleAdminUsers);
|
||||||
app.MapPost("/api/v1/admin/updateuser", (Delegate)HandleAdminUpdateUser);
|
app.MapPost("/api/v1/admin/updateuser", (Delegate)HandleAdminUpdateUser);
|
||||||
app.MapPost("/api/v1/admin/updatebot", (Delegate)HandleAdminUpdateBot);
|
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<IResult> 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<IPBanPayload>();
|
||||||
|
}
|
||||||
|
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<IResult> 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<BanUserPayload>();
|
||||||
|
}
|
||||||
|
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<IResult> HandleAdminUpdateBot(HttpContext context)
|
private static async Task<IResult> HandleAdminUpdateBot(HttpContext context)
|
||||||
|
@ -63,7 +218,7 @@ public static class AdminRoutes
|
||||||
// Check rank
|
// Check rank
|
||||||
var user = await Program.Database.GetUser(session.Username)
|
var user = await Program.Database.GetUser(session.Username)
|
||||||
?? throw new Exception("Could not lookup user from session");
|
?? 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;
|
context.Response.StatusCode = 403;
|
||||||
return Results.Json(new AdminUsersResponse
|
return Results.Json(new AdminUsersResponse
|
||||||
|
@ -93,6 +248,25 @@ public static class AdminRoutes
|
||||||
error = "No fields to update"
|
error = "No fields to update"
|
||||||
}, Utilities.JsonSerializerOptions);
|
}, 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
|
// Check rank
|
||||||
int? rank = payload.rank;
|
int? rank = payload.rank;
|
||||||
if (rank != null && rank < 1 || rank > 3)
|
if (rank != null && rank < 1 || rank > 3)
|
||||||
|
@ -193,6 +367,16 @@ public static class AdminRoutes
|
||||||
error = "Invalid rank"
|
error = "Invalid rank"
|
||||||
}, Utilities.JsonSerializerOptions);
|
}, 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
|
// Check developer
|
||||||
bool? developer = payload.developer;
|
bool? developer = payload.developer;
|
||||||
// Update rank
|
// Update rank
|
||||||
|
@ -257,7 +441,7 @@ public static class AdminRoutes
|
||||||
// Check rank
|
// Check rank
|
||||||
var user = await Program.Database.GetUser(session.Username)
|
var user = await Program.Database.GetUser(session.Username)
|
||||||
?? throw new Exception("Could not lookup user from session");
|
?? 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;
|
context.Response.StatusCode = 403;
|
||||||
return Results.Json(new AdminUsersResponse
|
return Results.Json(new AdminUsersResponse
|
||||||
|
@ -293,6 +477,7 @@ public static class AdminRoutes
|
||||||
email = user.Email,
|
email = user.Email,
|
||||||
rank = (int)user.Rank,
|
rank = (int)user.Rank,
|
||||||
banned = user.Banned,
|
banned = user.Banned,
|
||||||
|
banReason = user.BanReason ?? "",
|
||||||
dateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd"),
|
dateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd"),
|
||||||
dateJoined = user.Joined.ToString("yyyy-MM-dd HH:mm:ss"),
|
dateJoined = user.Joined.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
registrationIp = user.RegistrationIP.ToString(),
|
registrationIp = user.RegistrationIP.ToString(),
|
||||||
|
|
|
@ -63,7 +63,7 @@ public static class DeveloperRoutes
|
||||||
// Check developer status
|
// Check developer status
|
||||||
var user = await Program.Database.GetUser(session.Username) ??
|
var user = await Program.Database.GetUser(session.Username) ??
|
||||||
throw new Exception("Unable to get user from session");
|
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;
|
context.Response.StatusCode = 403;
|
||||||
return Results.Json(new CreateBotResponse
|
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."
|
error = "You must be an approved developer to create and manage bots."
|
||||||
}, Utilities.JsonSerializerOptions);
|
}, Utilities.JsonSerializerOptions);
|
||||||
}
|
}
|
||||||
// owner can only be specified by admins
|
// owner can only be specified by admins and moderators
|
||||||
if (payload.owner != null && user.Rank != Rank.Admin)
|
if (payload.owner != null && user.Rank != Rank.Admin && user.Rank != Rank.Moderator)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 403;
|
context.Response.StatusCode = 403;
|
||||||
return Results.Json(new ListBotsResponse
|
return Results.Json(new ListBotsResponse
|
||||||
|
@ -84,7 +84,7 @@ public static class DeveloperRoutes
|
||||||
}
|
}
|
||||||
// Get bots
|
// Get bots
|
||||||
// If the user is not an admin, they can only see their own 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,
|
id = (int)bot.Id,
|
||||||
username = bot.Username,
|
username = bot.Username,
|
||||||
|
|
9
CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs
Normal file
9
CollabVMAuthServer/HTTP/Payloads/BanUserPayload.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
9
CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs
Normal file
9
CollabVMAuthServer/HTTP/Payloads/IPBanPayload.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ public class AdminUser
|
||||||
public string email { get; set; }
|
public string email { get; set; }
|
||||||
public int rank { get; set; }
|
public int rank { get; set; }
|
||||||
public bool banned { get; set; }
|
public bool banned { get; set; }
|
||||||
|
public string banReason { get; set; }
|
||||||
public string dateOfBirth { get; set; }
|
public string dateOfBirth { get; set; }
|
||||||
public string dateJoined { get; set; }
|
public string dateJoined { get; set; }
|
||||||
public string registrationIp { get; set; }
|
public string registrationIp { get; set; }
|
||||||
|
|
7
CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs
Normal file
7
CollabVMAuthServer/HTTP/Responses/BanUserResponse.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||||
|
|
||||||
|
public class BanUserResponse
|
||||||
|
{
|
||||||
|
public bool success { get; set; }
|
||||||
|
public string? error { get; set; }
|
||||||
|
}
|
7
CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs
Normal file
7
CollabVMAuthServer/HTTP/Responses/IPBanResponse.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||||
|
|
||||||
|
public class IPBanResponse
|
||||||
|
{
|
||||||
|
public bool success { get; set; }
|
||||||
|
public string? error { get; set; }
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ public class JoinResponse
|
||||||
{
|
{
|
||||||
public bool success { get; set; }
|
public bool success { get; set; }
|
||||||
public bool clientSuccess { get; set; } = false;
|
public bool clientSuccess { get; set; } = false;
|
||||||
|
public bool? banned { get; set; } = null;
|
||||||
|
public string? banReason { get; set; }
|
||||||
public string? error { get; set; }
|
public string? error { get; set; }
|
||||||
public string? username { get; set; }
|
public string? username { get; set; }
|
||||||
public Rank? rank { get; set; }
|
public Rank? rank { get; set; }
|
||||||
|
|
|
@ -556,7 +556,9 @@ public static class Routes
|
||||||
{
|
{
|
||||||
success = true,
|
success = true,
|
||||||
clientSuccess = false,
|
clientSuccess = false,
|
||||||
error = "You are banned"
|
error = "Banned",
|
||||||
|
banned = true,
|
||||||
|
banReason = ban.Reason
|
||||||
}, Utilities.JsonSerializerOptions);
|
}, Utilities.JsonSerializerOptions);
|
||||||
}
|
}
|
||||||
// Check if session is valid
|
// Check if session is valid
|
||||||
|
@ -592,7 +594,9 @@ public static class Routes
|
||||||
{
|
{
|
||||||
success = true,
|
success = true,
|
||||||
clientSuccess = false,
|
clientSuccess = false,
|
||||||
error = "You are banned",
|
banned = true,
|
||||||
|
error = "Banned",
|
||||||
|
banReason = user.BanReason
|
||||||
}, Utilities.JsonSerializerOptions);
|
}, Utilities.JsonSerializerOptions);
|
||||||
}
|
}
|
||||||
// Update session
|
// Update session
|
||||||
|
@ -1026,33 +1030,29 @@ public static class Routes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Create the account
|
// Create the account
|
||||||
|
string? token = null;
|
||||||
if (Program.Config.Registration.EmailVerificationRequired)
|
if (Program.Config.Registration.EmailVerificationRequired)
|
||||||
{
|
{
|
||||||
var code = Program.Random.Next(10000000, 99999999).ToString();
|
var code = Program.Random.Next(10000000, 99999999).ToString();
|
||||||
await Program.Database.RegisterAccount(payload.username, payload.email, dob, payload.password, false, ip,code);
|
await Program.Database.RegisterAccount(payload.username, payload.email, dob, payload.password, false, ip,code);
|
||||||
await Program.Mailer.SendVerificationCode(payload.username, payload.email, 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
|
else
|
||||||
{
|
{
|
||||||
await Program.Database.RegisterAccount(payload.username, payload.email, dob, payload.password, true, ip, null);
|
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);
|
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)
|
private static IResult HandleInfo(HttpContext context)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
using Computernewb.CollabVMAuthServer.HTTP;
|
using Computernewb.CollabVMAuthServer.HTTP;
|
||||||
using Tomlet;
|
using Tomlet;
|
||||||
|
|
||||||
|
@ -14,7 +15,8 @@ public class Program
|
||||||
public static readonly Random Random = new Random();
|
public static readonly Random Random = new Random();
|
||||||
public static async Task Main(string[] args)
|
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
|
// Read config.toml
|
||||||
string configraw;
|
string configraw;
|
||||||
try
|
try
|
||||||
|
@ -39,8 +41,20 @@ public class Program
|
||||||
}
|
}
|
||||||
// Initialize database
|
// Initialize database
|
||||||
Database = new Database(Config.MySQL);
|
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, "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
|
// Create mailer
|
||||||
if (!Config.SMTP.Enabled && Config.Registration.EmailVerificationRequired)
|
if (!Config.SMTP.Enabled && Config.Registration.EmailVerificationRequired)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,7 @@ public class User
|
||||||
public string? PasswordResetCode { get; set; }
|
public string? PasswordResetCode { get; set; }
|
||||||
public Rank Rank { get; set; }
|
public Rank Rank { get; set; }
|
||||||
public bool Banned { get; set; }
|
public bool Banned { get; set; }
|
||||||
|
public string? BanReason { get; set; }
|
||||||
public IPAddress RegistrationIP { get; set; }
|
public IPAddress RegistrationIP { get; set; }
|
||||||
public DateTime Joined { get; set; }
|
public DateTime Joined { get; set; }
|
||||||
public bool Developer { get; set; }
|
public bool Developer { get; set; }
|
||||||
|
|
|
@ -63,7 +63,7 @@ public static class Utilities
|
||||||
case LogLevel.WARN:
|
case LogLevel.WARN:
|
||||||
case LogLevel.ERROR:
|
case LogLevel.ERROR:
|
||||||
case LogLevel.FATAL:
|
case LogLevel.FATAL:
|
||||||
Console.Error.Write(logstr.ToString());
|
Console.Error.WriteLine(logstr.ToString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue