whole bunch of shit
- admin routes - developer status and bots - probably a few other tweaks i forgot
This commit is contained in:
parent
b627ab383f
commit
c68451cf07
21 changed files with 874 additions and 34 deletions
11
CollabVMAuthServer/Bot.cs
Normal file
11
CollabVMAuthServer/Bot.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Computernewb.CollabVMAuthServer;
|
||||
|
||||
public class Bot
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Token { get; set; }
|
||||
public Rank Rank { get; set; }
|
||||
public string Owner { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
}
|
|
@ -37,7 +37,9 @@ public class Database
|
|||
password_reset_code CHAR(8) DEFAULT NULL,
|
||||
cvm_rank INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
banned BOOLEAN NOT NULL DEFAULT 0,
|
||||
registration_ip VARBINARY(16) NOT NULL
|
||||
registration_ip VARBINARY(16) NOT NULL,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
developer BOOLEAN NOT NULL DEFAULT 0
|
||||
);
|
||||
""";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
@ -62,6 +64,18 @@ public class Database
|
|||
)
|
||||
""";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
cmd.CommandText = """
|
||||
CREATE TABLE IF NOT EXISTS bots (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(20) NOT NULL UNIQUE KEY,
|
||||
token CHAR(64) NOT NULL UNIQUE KEY,
|
||||
cvm_rank INT UNSIGNED NOT NULL DEFAULT 1,
|
||||
owner VARCHAR(20) NOT NULL,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (owner) REFERENCES users(username) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
)
|
||||
""";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<User?> GetUser(string? username = null, string? email = null)
|
||||
|
@ -96,7 +110,9 @@ 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"),
|
||||
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip"))
|
||||
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
||||
Joined = reader.GetDateTime("created"),
|
||||
Developer = reader.GetBoolean("developer")
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -230,7 +246,7 @@ public class Database
|
|||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateUser(string username, string? newUsername = null, string? newPassword = null, string? newEmail = null)
|
||||
public async Task UpdateUser(string username, string? newUsername = null, string? newPassword = null, string? newEmail = null, int? newRank = null, bool? developer = null)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
|
@ -251,6 +267,17 @@ public class Database
|
|||
updates.Add("email = @newEmail");
|
||||
cmd.Parameters.AddWithValue("@newEmail", newEmail);
|
||||
}
|
||||
|
||||
if (newRank != null)
|
||||
{
|
||||
updates.Add("cvm_rank = @newRank");
|
||||
cmd.Parameters.AddWithValue("@newRank", newRank);
|
||||
}
|
||||
if (developer != null)
|
||||
{
|
||||
updates.Add("developer = @developer");
|
||||
cmd.Parameters.AddWithValue("@developer", developer);
|
||||
}
|
||||
cmd.CommandText = $"UPDATE users SET {string.Join(", ", updates)} WHERE username = @username";
|
||||
cmd.Parameters.AddWithValue("@username", username);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
@ -307,4 +334,148 @@ public class Database
|
|||
cmd.Parameters.AddWithValue("@username", username);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<User[]> ListUsers(string? filterUsername = null, string orderBy = "id", bool descending = false)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
var where = new List<string>();
|
||||
if (filterUsername != null)
|
||||
{
|
||||
where.Add("username LIKE @filterUsername");
|
||||
cmd.Parameters.AddWithValue("@filterUsername", filterUsername);
|
||||
}
|
||||
cmd.CommandText = $"SELECT * FROM users {(where.Count > 0 ? "WHERE" : "")} {string.Join(" AND ", where)} ORDER BY {orderBy} {(descending ? "DESC" : "ASC")}";
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
var users = new List<User>();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
users.Add(new User
|
||||
{
|
||||
Id = reader.GetUInt32("id"),
|
||||
Username = reader.GetString("username"),
|
||||
Password = reader.GetString("password"),
|
||||
Email = reader.GetString("email"),
|
||||
DateOfBirth = reader.GetDateOnly("date_of_birth"),
|
||||
EmailVerified = reader.GetBoolean("email_verified"),
|
||||
EmailVerificationCode = reader.IsDBNull("email_verification_code") ? null : reader.GetString("email_verification_code"),
|
||||
PasswordResetCode = reader.IsDBNull("password_reset_code") ? null : reader.GetString("password_reset_code"),
|
||||
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
||||
Banned = reader.GetBoolean("banned"),
|
||||
RegistrationIP = new IPAddress(reader.GetFieldValue<byte[]>("registration_ip")),
|
||||
Joined = reader.GetDateTime("created"),
|
||||
Developer = reader.GetBoolean("developer")
|
||||
});
|
||||
}
|
||||
return users.ToArray();
|
||||
}
|
||||
|
||||
public async Task CreateBot(string username, string token, string owner)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
cmd.CommandText = "INSERT INTO bots (username, token, owner) VALUES (@username, @token, @owner)";
|
||||
cmd.Parameters.AddWithValue("@username", username);
|
||||
cmd.Parameters.AddWithValue("@token", token);
|
||||
cmd.Parameters.AddWithValue("@owner", owner);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<Bot[]> ListBots(string? owner = null)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
var where = new List<string>();
|
||||
if (owner != null)
|
||||
{
|
||||
where.Add("owner = @owner");
|
||||
cmd.Parameters.AddWithValue("@owner", owner);
|
||||
}
|
||||
cmd.CommandText = $"SELECT * FROM bots {(where.Count > 0 ? "WHERE" : "")} {string.Join(" AND ", where)}";
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
var bots = new List<Bot>();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
bots.Add(new Bot
|
||||
{
|
||||
Id = reader.GetUInt32("id"),
|
||||
Username = reader.GetString("username"),
|
||||
Token = reader.GetString("token"),
|
||||
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
||||
Owner = reader.GetString("owner"),
|
||||
Created = reader.GetDateTime("created")
|
||||
});
|
||||
}
|
||||
return bots.ToArray();
|
||||
}
|
||||
|
||||
public async Task UpdateBot(string username, string? newUsername = null, string? newToken = null, int? newRank = null)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
var updates = new List<string>();
|
||||
if (newUsername != null)
|
||||
{
|
||||
updates.Add("username = @username");
|
||||
cmd.Parameters.AddWithValue("@username", newUsername);
|
||||
}
|
||||
if (newToken != null)
|
||||
{
|
||||
updates.Add("token = @token");
|
||||
cmd.Parameters.AddWithValue("@token", newToken);
|
||||
}
|
||||
if (newRank != null)
|
||||
{
|
||||
updates.Add("cvm_rank = @rank");
|
||||
cmd.Parameters.AddWithValue("@rank", newRank);
|
||||
}
|
||||
cmd.CommandText = $"UPDATE bots SET {string.Join(", ", updates)} WHERE username = @username";
|
||||
cmd.Parameters.AddWithValue("@username", username);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteBots(string owner)
|
||||
{
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
cmd.CommandText = "DELETE FROM bots WHERE owner = @owner";
|
||||
cmd.Parameters.AddWithValue("@owner", owner);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<Bot?> GetBot(string? username = null, string? token = null)
|
||||
{
|
||||
if (username == null && token == null)
|
||||
throw new ArgumentException("username or token must be provided");
|
||||
await using var db = new MySqlConnection(connectionString);
|
||||
await db.OpenAsync();
|
||||
await using var cmd = db.CreateCommand();
|
||||
if (username != null)
|
||||
{
|
||||
cmd.CommandText = "SELECT * FROM bots WHERE username = @username";
|
||||
cmd.Parameters.AddWithValue("@username", username);
|
||||
}
|
||||
else if (token != null)
|
||||
{
|
||||
cmd.CommandText = "SELECT * FROM bots WHERE token = @token";
|
||||
cmd.Parameters.AddWithValue("@token", token);
|
||||
}
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
if (!await reader.ReadAsync())
|
||||
return null;
|
||||
return new Bot
|
||||
{
|
||||
Id = reader.GetUInt32("id"),
|
||||
Username = reader.GetString("username"),
|
||||
Token = reader.GetString("token"),
|
||||
Rank = (Rank)reader.GetUInt32("cvm_rank"),
|
||||
Owner = reader.GetString("owner"),
|
||||
Created = reader.GetDateTime("created")
|
||||
};
|
||||
}
|
||||
}
|
309
CollabVMAuthServer/HTTP/AdminRoutes.cs
Normal file
309
CollabVMAuthServer/HTTP/AdminRoutes.cs
Normal file
|
@ -0,0 +1,309 @@
|
|||
using System.Text.Json;
|
||||
using Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
using Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
namespace Computernewb.CollabVMAuthServer.HTTP;
|
||||
|
||||
public static class AdminRoutes
|
||||
{
|
||||
public static void RegisterRoutes(IEndpointRouteBuilder app)
|
||||
{
|
||||
app.MapPost("/api/v1/admin/users", (Delegate)HandleAdminUsers);
|
||||
app.MapPost("/api/v1/admin/updateuser", (Delegate)HandleAdminUpdateUser);
|
||||
app.MapPost("/api/v1/admin/updatebot", (Delegate)HandleAdminUpdateBot);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleAdminUpdateBot(HttpContext context)
|
||||
{
|
||||
// Check payload
|
||||
if (context.Request.ContentType != "application/json")
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
AdminUpdateBotPayload? payload;
|
||||
try
|
||||
{
|
||||
payload = await context.Request.ReadFromJsonAsync<AdminUpdateBotPayload>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}");
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.token) || string.IsNullOrWhiteSpace(payload.username))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
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 AdminUpdateBotResponse
|
||||
{
|
||||
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)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Insufficient permissions"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check target bot
|
||||
var targetBot = await Program.Database.GetBot(payload.username);
|
||||
if (targetBot == null)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Bot not found"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Make sure at least one field is being updated
|
||||
if (payload.rank == null)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "No fields to update"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check rank
|
||||
int? rank = payload.rank;
|
||||
if (rank != null && rank < 1 || rank > 3)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid rank"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Update rank
|
||||
await Program.Database.UpdateBot(targetBot.Username, newRank: payload.rank);
|
||||
return Results.Json(new AdminUpdateBotResponse
|
||||
{
|
||||
success = true
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleAdminUpdateUser(HttpContext context)
|
||||
{
|
||||
// Check payload
|
||||
if (context.Request.ContentType != "application/json")
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateUserResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
AdminUpdateUserPayload? payload;
|
||||
try
|
||||
{
|
||||
payload = await context.Request.ReadFromJsonAsync<AdminUpdateUserPayload>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}");
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateUserResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.token) || string.IsNullOrWhiteSpace(payload.username))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateUserResponse
|
||||
{
|
||||
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 AdminUpdateUserResponse
|
||||
{
|
||||
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)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
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 AdminUpdateUserResponse
|
||||
{
|
||||
success = false,
|
||||
error = "User not found"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check rank
|
||||
int? rank = payload.rank;
|
||||
if (rank != null && rank < 1 || rank > 3)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUpdateUserResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid rank"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check developer
|
||||
bool? developer = payload.developer;
|
||||
// Update rank
|
||||
await Program.Database.UpdateUser(targetUser.Username, newRank: payload.rank, developer: developer);
|
||||
if (developer == false)
|
||||
{
|
||||
await Program.Database.DeleteBots(targetUser.Username);
|
||||
}
|
||||
return Results.Json(new AdminUpdateUserResponse
|
||||
{
|
||||
success = true
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleAdminUsers(HttpContext context)
|
||||
{
|
||||
// Check payload
|
||||
if (context.Request.ContentType != "application/json")
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
AdminUsersPayload? payload;
|
||||
try
|
||||
{
|
||||
payload = await context.Request.ReadFromJsonAsync<AdminUsersPayload>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}");
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.token) || payload.page < 1 || payload.resultsPerPage < 1)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
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 AdminUsersResponse
|
||||
{
|
||||
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)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Insufficient permissions"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Validate orderBy
|
||||
if (payload.orderBy != null && !new string[] { "id", "username", "email", "date_of_birth", "cvm_rank", "banned", "created" }.Contains(payload.orderBy))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid orderBy"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Get users
|
||||
string? filterUsername = null;
|
||||
if (payload.filterUsername != null)
|
||||
{
|
||||
filterUsername = "%" + payload.filterUsername
|
||||
.Replace("%", "!%")
|
||||
.Replace("!", "!!")
|
||||
.Replace("_", "!_")
|
||||
.Replace("[", "![") + "%";
|
||||
}
|
||||
var users = (await Program.Database.ListUsers(filterUsername, payload.orderBy ?? "id", payload.orderByDescending)).Select(user => new AdminUser
|
||||
{
|
||||
id = user.Id,
|
||||
username = user.Username,
|
||||
email = user.Email,
|
||||
rank = (int)user.Rank,
|
||||
banned = user.Banned,
|
||||
dateOfBirth = user.DateOfBirth.ToString("yyyy-MM-dd"),
|
||||
dateJoined = user.Joined.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
registrationIp = user.RegistrationIP.ToString(),
|
||||
developer = user.Developer
|
||||
}).ToArray();
|
||||
var page = users.Skip((payload.page - 1) * payload.resultsPerPage).Take(payload.resultsPerPage).ToArray();
|
||||
return Results.Json(new AdminUsersResponse
|
||||
{
|
||||
success = true,
|
||||
users = page,
|
||||
totalPageCount = (int)Math.Ceiling(users.Length / (double)payload.resultsPerPage)
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
}
|
196
CollabVMAuthServer/HTTP/DeveloperRoutes.cs
Normal file
196
CollabVMAuthServer/HTTP/DeveloperRoutes.cs
Normal file
|
@ -0,0 +1,196 @@
|
|||
using System.Text.Json;
|
||||
using Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
using Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
namespace Computernewb.CollabVMAuthServer.HTTP;
|
||||
|
||||
public static class DeveloperRoutes
|
||||
{
|
||||
public static void RegisterRoutes(IEndpointRouteBuilder app)
|
||||
{
|
||||
app.MapPost("/api/v1/bots/create", (Delegate)HandleCreateBot);
|
||||
app.MapPost("/api/v1/bots/list", (Delegate)HandleListBots);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleListBots(HttpContext context)
|
||||
{
|
||||
// Check payload
|
||||
if (context.Request.ContentType != "application/json")
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new ListBotsResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
ListBotsPayload? payload;
|
||||
try
|
||||
{
|
||||
payload = await context.Request.ReadFromJsonAsync<ListBotsPayload>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}");
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new ListBotsResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.token) || payload.resultsPerPage <= 0 ||
|
||||
payload.page <= 0)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new ListBotsResponse
|
||||
{
|
||||
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 ListBotsResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid session"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
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)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new ListBotsResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Insufficient permissions"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// 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
|
||||
{
|
||||
id = (int)bot.Id,
|
||||
username = bot.Username,
|
||||
rank = (int)bot.Rank,
|
||||
owner = bot.Owner,
|
||||
created = bot.Created.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
});
|
||||
var page = bots.Skip((payload.page - 1) * payload.resultsPerPage).Take(payload.resultsPerPage).ToArray();
|
||||
return Results.Json(new ListBotsResponse
|
||||
{
|
||||
success = true,
|
||||
totalPageCount = (int)Math.Ceiling(bots.Count() / (double)payload.resultsPerPage),
|
||||
bots = page
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleCreateBot(HttpContext context)
|
||||
{
|
||||
// Check payload
|
||||
if (context.Request.ContentType != "application/json")
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
CreateBotPayload? payload;
|
||||
try
|
||||
{
|
||||
payload = await context.Request.ReadFromJsonAsync<CreateBotPayload>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Utilities.Log(LogLevel.DEBUG, $"Failed to parse JSON: {ex.Message}");
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid request body"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.token) || string.IsNullOrWhiteSpace(payload.username))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
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 CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid session"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check developer status
|
||||
var user = await Program.Database.GetUser(session.Username) ??
|
||||
throw new Exception("Unable to get user from session");
|
||||
if (!user.Developer)
|
||||
{
|
||||
context.Response.StatusCode = 403;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "You must be an approved developer to create and manage bots."
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check bot username
|
||||
if (await Program.Database.GetBot(payload.username) != null ||
|
||||
await Program.Database.GetUser(payload.username) != null)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error = "That username is taken."
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
if (!Utilities.ValidateUsername(payload.username))
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = false,
|
||||
error =
|
||||
"Usernames can contain only numbers, letters, spaces, dashes, underscores, and dots, and must be between 3 and 20 characters."
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Generate token
|
||||
string token = Utilities.RandomString(64);
|
||||
// Create bot
|
||||
await Program.Database.CreateBot(payload.username, token, user.Username);
|
||||
return Results.Json(new CreateBotResponse
|
||||
{
|
||||
success = true,
|
||||
token = token
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
|
||||
public class AdminUpdateBotPayload
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string username { get; set; }
|
||||
public int? rank { get; set; }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
|
||||
public class AdminUpdateUserPayload
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string username { get; set; }
|
||||
public int? rank { get; set; }
|
||||
public bool? developer { get; set; } = null;
|
||||
}
|
11
CollabVMAuthServer/HTTP/Payloads/AdminUsersPayload.cs
Normal file
11
CollabVMAuthServer/HTTP/Payloads/AdminUsersPayload.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
|
||||
public class AdminUsersPayload
|
||||
{
|
||||
public string token { get; set; }
|
||||
public int resultsPerPage { get; set; }
|
||||
public int page { get; set; }
|
||||
public string? filterUsername { get; set; }
|
||||
public string? orderBy { get; set; }
|
||||
public bool orderByDescending { get; set; } = false;
|
||||
}
|
7
CollabVMAuthServer/HTTP/Payloads/CreateBotPayload.cs
Normal file
7
CollabVMAuthServer/HTTP/Payloads/CreateBotPayload.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
|
||||
public class CreateBotPayload
|
||||
{
|
||||
public string token { get; set; }
|
||||
public string username { get; set; }
|
||||
}
|
9
CollabVMAuthServer/HTTP/Payloads/ListBotsPayload.cs
Normal file
9
CollabVMAuthServer/HTTP/Payloads/ListBotsPayload.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Payloads;
|
||||
|
||||
public class ListBotsPayload
|
||||
{
|
||||
public string token { get; set; }
|
||||
public int resultsPerPage { get; set; }
|
||||
public int page { get; set; }
|
||||
public string? owner { get; set; }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class AdminUpdateBotResponse
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public string? error { get; set; }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class AdminUpdateUserResponse
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public string? error { get; set; }
|
||||
}
|
22
CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs
Normal file
22
CollabVMAuthServer/HTTP/Responses/AdminUsersResponse.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class AdminUsersResponse
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public string? error { get; set; }
|
||||
public int? totalPageCount { get; set; } = null;
|
||||
public AdminUser[]? users { get; set; }
|
||||
}
|
||||
|
||||
public class AdminUser
|
||||
{
|
||||
public uint id { get; set; }
|
||||
public string username { get; set; }
|
||||
public string email { get; set; }
|
||||
public int rank { get; set; }
|
||||
public bool banned { get; set; }
|
||||
public string dateOfBirth { get; set; }
|
||||
public string dateJoined { get; set; }
|
||||
public string registrationIp { get; set; }
|
||||
public bool developer { get; set; }
|
||||
}
|
8
CollabVMAuthServer/HTTP/Responses/CreateBotResponse.cs
Normal file
8
CollabVMAuthServer/HTTP/Responses/CreateBotResponse.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class CreateBotResponse
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public string? error { get; set; }
|
||||
public string? token { get; set; }
|
||||
}
|
10
CollabVMAuthServer/HTTP/Responses/ListBot.cs
Normal file
10
CollabVMAuthServer/HTTP/Responses/ListBot.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class ListBot
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string username { get; set; }
|
||||
public int rank { get; set; }
|
||||
public string owner { get; set; }
|
||||
public string created { get; set; }
|
||||
}
|
9
CollabVMAuthServer/HTTP/Responses/ListBotsResponse.cs
Normal file
9
CollabVMAuthServer/HTTP/Responses/ListBotsResponse.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Computernewb.CollabVMAuthServer.HTTP.Responses;
|
||||
|
||||
public class ListBotsResponse
|
||||
{
|
||||
public bool success { get; set; }
|
||||
public string? error { get; set; }
|
||||
public int? totalPageCount { get; set; } = null;
|
||||
public ListBot[]? bots { get; set; }
|
||||
}
|
|
@ -8,4 +8,5 @@ public class LoginResponse
|
|||
public bool? verificationRequired { get; set; }
|
||||
public string? email { get; set; }
|
||||
public string? username { get; set; }
|
||||
public int rank { get; set; }
|
||||
}
|
|
@ -7,4 +7,5 @@ public class SessionResponse
|
|||
public bool banned { get; set; } = false;
|
||||
public string? username { get; set; }
|
||||
public string? email { get; set; }
|
||||
public int rank { get; set; }
|
||||
}
|
|
@ -269,8 +269,7 @@ public static class Routes
|
|||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Make sure username isn't taken
|
||||
var _user = await Program.Database.GetUser(payload.username);
|
||||
if (_user != null)
|
||||
if (await Program.Database.GetUser(payload.username) != null || await Program.Database.GetBot(payload.username) != null)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new RegisterResponse
|
||||
|
@ -462,7 +461,7 @@ public static class Routes
|
|||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check if session is expired
|
||||
if (DateTime.Now > session.LastUsed.AddDays(Program.Config.Accounts.SessionExpiryDays))
|
||||
if (Utilities.IsSessionExpired(session))
|
||||
{
|
||||
return Results.Json(new SessionResponse
|
||||
{
|
||||
|
@ -477,7 +476,8 @@ public static class Routes
|
|||
success = true,
|
||||
banned = user.Banned,
|
||||
username = user.Username,
|
||||
email = user.Email
|
||||
email = user.Email,
|
||||
rank = (int)user.Rank
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
|
@ -549,6 +549,9 @@ public static class Routes
|
|||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
// Check if session is valid
|
||||
if (payload.sessionToken.Length == 32)
|
||||
{
|
||||
// User
|
||||
var session = await Program.Database.GetSession(payload.sessionToken);
|
||||
if (session == null)
|
||||
{
|
||||
|
@ -590,6 +593,36 @@ public static class Routes
|
|||
username = session.Username,
|
||||
rank = user.Rank
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
} else if (payload.sessionToken.Length == 64)
|
||||
{
|
||||
// Bot
|
||||
var bot = await Program.Database.GetBot(token: payload.sessionToken);
|
||||
if (bot == null)
|
||||
{
|
||||
return Results.Json(new JoinResponse
|
||||
{
|
||||
success = true,
|
||||
clientSuccess = false,
|
||||
error = "Invalid session",
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
return Results.Json(new JoinResponse
|
||||
{
|
||||
success = true,
|
||||
clientSuccess = true,
|
||||
username = bot.Username,
|
||||
rank = bot.Rank
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new JoinResponse
|
||||
{
|
||||
success = false,
|
||||
error = "Invalid session"
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleLogin(HttpContext context)
|
||||
|
@ -689,6 +722,7 @@ public static class Routes
|
|||
verificationRequired = true,
|
||||
email = user.Email,
|
||||
username = user.Username,
|
||||
rank = (int)user.Rank
|
||||
});
|
||||
}
|
||||
// Check max sessions
|
||||
|
@ -706,7 +740,8 @@ public static class Routes
|
|||
success = true,
|
||||
token = token,
|
||||
username = user.Username,
|
||||
email = user.Email
|
||||
email = user.Email,
|
||||
rank = (int)user.Rank
|
||||
}, Utilities.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
|
@ -876,7 +911,7 @@ public static class Routes
|
|||
}
|
||||
// Make sure username isn't taken
|
||||
var user = await Program.Database.GetUser(payload.username);
|
||||
if (user != null)
|
||||
if (user != null || await Program.Database.GetBot(payload.username) != null)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return Results.Json(new RegisterResponse
|
||||
|
|
|
@ -74,6 +74,8 @@ public class Program
|
|||
app.Lifetime.ApplicationStarted.Register(() => Utilities.Log(LogLevel.INFO, $"Webserver listening on {Config.HTTP.Host}:{Config.HTTP.Port}"));
|
||||
// Register routes
|
||||
Routes.RegisterRoutes(app);
|
||||
AdminRoutes.RegisterRoutes(app);
|
||||
DeveloperRoutes.RegisterRoutes(app);
|
||||
app.Run();
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ public class User
|
|||
public Rank Rank { get; set; }
|
||||
public bool Banned { get; set; }
|
||||
public IPAddress RegistrationIP { get; set; }
|
||||
public DateTime Joined { get; set; }
|
||||
public bool Developer { get; set; }
|
||||
}
|
||||
|
||||
public enum Rank : uint
|
||||
|
|
|
@ -120,4 +120,9 @@ public static class Utilities
|
|||
}
|
||||
else return ctx.Connection.RemoteIpAddress;
|
||||
}
|
||||
|
||||
public static bool IsSessionExpired(Session session)
|
||||
{
|
||||
return DateTime.Now > session.LastUsed.AddDays(Program.Config.Accounts.SessionExpiryDays);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue