Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

11 changed files with 10 additions and 175 deletions

@ -1 +1 @@
Subproject commit cd42387f6e4610341b15e0859f78f8a394914aae Subproject commit f0de933380c0c6488306d04b340f9a1599a4d217

View file

@ -1,41 +0,0 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using CollabVM.Server.Config;
namespace CollabVM.Server;
public class AuthManager
{
private readonly AuthConfig config;
public AuthManager(AuthConfig config)
{
this.config = config;
}
public async Task<JoinResponse> AuthenticateUser(string token, User user)
{
using var http = new HttpClient();
var body = new StringContent(
JsonSerializer.Serialize(new
{
secretKey = this.config.SecretKey,
sessionToken = token,
ip = user.IP.ToString(),
}), Encoding.UTF8, "application/json");
body.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await http.PostAsync(this.config.AuthServerURI + "/api/v1/join", body);
return await response.Content.ReadFromJsonAsync<JoinResponse>() ?? throw new Exception("Failed to parse JSON response from auth server.");
}
}
public class JoinResponse
{
public bool success { get; set; }
public bool clientSuccess { get; set; } = false;
public string? error { get; set; }
public string? username { get; set; }
public Rank? rank { get; set; }
}

View file

@ -3,7 +3,6 @@ namespace CollabVM.Server.Config;
public class IConfig public class IConfig
{ {
public HTTPConfig HTTP { get; set; } public HTTPConfig HTTP { get; set; }
public AuthConfig Auth { get; set; }
public TurnConfig Turns { get; set; } public TurnConfig Turns { get; set; }
public VoteConfig Votes { get; set; } public VoteConfig Votes { get; set; }
public ChatConfig Chat { get; set; } public ChatConfig Chat { get; set; }
@ -119,19 +118,4 @@ public class QEMUVMConfig
public int QMPPort { get; set; } public int QMPPort { get; set; }
public string? QMPSocketDir { get; set; } public string? QMPSocketDir { get; set; }
public bool Snapshots { get; set; } public bool Snapshots { get; set; }
}
public class AuthConfig
{
public bool Enabled { get; set; }
public string? AuthServerURI { get; set; }
public string? SecretKey { get; set; }
public AuthConfigGuestPermissions GuestPermissions { get; set; }
}
public class AuthConfigGuestPermissions
{
public bool Chat { get; set; }
public bool Turns { get; set; }
public bool Vote { get; set; }
} }

View file

@ -17,7 +17,6 @@ class Program
public static Random rnd = new(); public static Random rnd = new();
public static IConfig Config; public static IConfig Config;
public static Database? Database; public static Database? Database;
public static AuthManager AuthManager;
// These can go here for now, might move them later // These can go here for now, might move them later
public static readonly string ScreenHiddenBase64 = Convert.ToBase64String(Utilities.GetAsset("screenhidden.jpeg")); public static readonly string ScreenHiddenBase64 = Convert.ToBase64String(Utilities.GetAsset("screenhidden.jpeg"));
public static readonly byte[] ScreenHiddenThumb = Utilities.GetAsset("screenhiddenthumb.jpeg"); public static readonly byte[] ScreenHiddenThumb = Utilities.GetAsset("screenhiddenthumb.jpeg");
@ -57,12 +56,6 @@ class Program
await Database.OpenAsync(); await Database.OpenAsync();
Utilities.Log(LogLevel.INFO, "Connected to MySQL Database."); Utilities.Log(LogLevel.INFO, "Connected to MySQL Database.");
} }
// Initialize auth
if (Config.Auth.Enabled)
{
AuthManager = new AuthManager(Config.Auth);
Utilities.Log(LogLevel.INFO, $"Account authentication enabled (Server: {Config.Auth.AuthServerURI})");
}
// Initialize the VMs // Initialize the VMs
VMManager = new VMManager(Config.VMs); VMManager = new VMManager(Config.VMs);
await VMManager.StartAll(); await VMManager.StartAll();

View file

@ -3,7 +3,6 @@ namespace CollabVM.Server;
public enum Rank public enum Rank
{ {
Unregistered = 0, Unregistered = 0,
Registered = 1,
Admin = 2, Admin = 2,
Moderator = 3, Moderator = 3,
} }

View file

@ -64,7 +64,6 @@ public class User
this.Disconnected += OnDisconnected; this.Disconnected += OnDisconnected;
this.ConnectedToVM += delegate { }; this.ConnectedToVM += delegate { };
this.Renamed += delegate { }; this.Renamed += delegate { };
if (Program.Config.Auth.Enabled) SendAsync(Guacutils.Encode("auth", Program.Config.Auth.AuthServerURI!));
SendAsync("3.nop;"); SendAsync("3.nop;");
ReadLoop(tokenSource.Token); ReadLoop(tokenSource.Token);
} }
@ -123,25 +122,6 @@ public class User
break; break;
case "rename": case "rename":
{ {
if (Program.Config.Auth.Enabled)
{
if (_rank != Rank.Unregistered)
{
await SendChat("", "Go to your account settings to change your username.");
return;
} else if (msgArr.Length != 1)
{
// Don't send an error message if the user doesn't yet have a username, since it was probably an automated attempt by the client
if (this._username != null)
{
await this.SendChat("", "You need to log in to do that.");
return;
}
// If the user doesn't have a username, assign them a guest username
await this.AssignGuestUsername(this.vm);
return;
}
}
if (msgArr.Length == 1) if (msgArr.Length == 1)
await this.AssignGuestUsername(this.vm); await this.AssignGuestUsername(this.vm);
else else
@ -197,10 +177,10 @@ public class User
chatmsg.Add(vm.Config.MOTD); chatmsg.Add(vm.Config.MOTD);
await this.SendAsync(Guacutils.Encode(chatmsg.ToArray())); await this.SendAsync(Guacutils.Encode(chatmsg.ToArray()));
var turn = vm.TurnQueue.CurrentTurn(); var turn = vm.TurnQueue.CurrentTurn();
await SendTurnUpdate(turn); if (turn.Queue.Length > 0) await SendTurnUpdate(turn);
if (vm.Vote != null) await SendVoteUpdate(vm.Vote.GetStatus()); if (vm.Vote != null) await SendVoteUpdate(vm.Vote.GetStatus());
this.ConnectedToVM.Invoke(this, vm.Config.ID); this.ConnectedToVM.Invoke(this, vm.Config.ID);
if (vm.ScreenHidden && (_rank == Rank.Unregistered || _rank == Rank.Registered)) if (vm.ScreenHidden && _rank == Rank.Unregistered)
{ {
await this.SendScreenSize(new(1024, 768)); await this.SendScreenSize(new(1024, 768));
await this.SendRect(Program.ScreenHiddenBase64, 0, 0); await this.SendRect(Program.ScreenHiddenBase64, 0, 0);
@ -212,52 +192,10 @@ public class User
} }
} }
break; break;
case "login":
{
if (this.vm == null || msgArr.Length != 2 || !Program.Config.Auth.Enabled) return;
var res = await Program.AuthManager.AuthenticateUser(msgArr[1], this);
if (!res.success)
{
Utilities.Log(LogLevel.ERROR, $"Failed to query auth server: {res.error}. Rejecting login.");
await this.SendAsync(Guacutils.Encode("login", "0", "Internal error"));
return;
}
if (!res.clientSuccess)
{
await this.SendAsync(Guacutils.Encode("login", "0", res.error ?? "Unknown error"));
if (res.error == "You are banned") await Close(true);
}
// Success
Utilities.Log(LogLevel.INFO, $"{_ip.ToString()} logged in as {res.username}");
await this.SendAsync(Guacutils.Encode("login", "1"));
var _user = vm!.Users.Find(u => u.Username == res.username);
if (_user != null)
{
// Only one login per user
await _user.Close();
}
await Rename(res.username);
this._rank = (Rank)res.rank!;
if (this._rank == Rank.Admin)
{
await this.SendAsync(Guacutils.Encode("admin", "0", "1"));
} else if (this._rank == Rank.Moderator)
{
await this.SendAsync(Guacutils.Encode("admin", "0", "3", Program.Config.ModPermissions.ToMask().ToString()));
}
await vm.ReannounceUser(this);
}
break;
case "chat": case "chat":
{ {
if (this.vm == null || msgArr.Length != 2 || IPData!.MuteStatus != MuteStatus.None) return; if (this.vm == null || msgArr.Length != 2 || IPData!.MuteStatus != MuteStatus.None) return;
if (Program.Config.Auth.Enabled && !Program.Config.Auth.GuestPermissions.Chat && if (_rank == Rank.Unregistered && ChatLimiter != null && !ChatLimiter.Limit())
_rank == Rank.Unregistered)
{
await this.SendChat("", "You need to login to do that.");
return;
}
if ((_rank == Rank.Unregistered || _rank == Rank.Registered) && ChatLimiter != null && !ChatLimiter.Limit())
{ {
if (IPData!.MuteStatus == MuteStatus.None) await Mute(false); if (IPData!.MuteStatus == MuteStatus.None) await Mute(false);
return; return;
@ -303,12 +241,6 @@ public class User
case "turn": case "turn":
{ {
if (this.vm == null || msgArr.Length > 2 || IPData!.MuteStatus != MuteStatus.None) return; if (this.vm == null || msgArr.Length > 2 || IPData!.MuteStatus != MuteStatus.None) return;
if (Program.Config.Auth.Enabled && !Program.Config.Auth.GuestPermissions.Turns &&
_rank == Rank.Unregistered)
{
await this.SendChat("", "You need to login to do that.");
return;
}
if (msgArr.Length == 1 || msgArr[1] == "1" && !IPData!.IsTurning && (vm.TurnsAllowed || turnsAllowed || _rank == Rank.Admin || _rank == Rank.Moderator)) if (msgArr.Length == 1 || msgArr[1] == "1" && !IPData!.IsTurning && (vm.TurnsAllowed || turnsAllowed || _rank == Rank.Admin || _rank == Rank.Moderator))
{ {
vm.TurnQueue.AddUser(this); vm.TurnQueue.AddUser(this);
@ -321,12 +253,6 @@ public class User
case "vote": case "vote":
{ {
if (this.vm == null || msgArr.Length != 2 || IPData!.IsVoting || IPData!.MuteStatus != MuteStatus.None) return; if (this.vm == null || msgArr.Length != 2 || IPData!.IsVoting || IPData!.MuteStatus != MuteStatus.None) return;
if (Program.Config.Auth.Enabled && !Program.Config.Auth.GuestPermissions.Vote &&
_rank == Rank.Unregistered)
{
await this.SendChat("", "You need to login to do that.");
return;
}
if (!vm.Controller.Snapshots) if (!vm.Controller.Snapshots)
{ {
await this.SendChat("", "This VM does not support voting to reset"); await this.SendChat("", "This VM does not support voting to reset");
@ -355,13 +281,6 @@ public class User
{ {
// Login // Login
if (msgArr.Length != 3) return; if (msgArr.Length != 3) return;
if (Program.Config.Auth.Enabled)
{
await this.SendChat("",
"This server does not support staff passwords. Please log in to become staff.");
await this.SendAsync(Guacutils.Encode("admin", "0", "0"));
return;
}
using var sha = SHA256.Create(); using var sha = SHA256.Create();
var hash = Utilities.BytesToHex(sha.ComputeHash(Encoding.UTF8.GetBytes(msgArr[2]))); var hash = Utilities.BytesToHex(sha.ComputeHash(Encoding.UTF8.GetBytes(msgArr[2])));
if (hash == Program.Config.Staff.AdminPasswordHash) if (hash == Program.Config.Staff.AdminPasswordHash)
@ -495,11 +414,6 @@ public class User
{ {
// Rename user // Rename user
if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Rename) || msgArr.Length != 4 || vm == null) return; if (_rank != Rank.Admin && (_rank != Rank.Moderator || !Program.Config.ModPermissions.Rename) || msgArr.Length != 4 || vm == null) return;
if (Program.Config.Auth.Enabled)
{
await this.SendChat("", "Cannot rename users on a server that uses authentication.");
return;
}
var user = vm.Users.First(u => u.Username == msgArr[2]); var user = vm.Users.First(u => u.Username == msgArr[2]);
if (user == null) return; if (user == null) return;
await user.Rename(msgArr[3]); await user.Rename(msgArr[3]);

View file

@ -59,7 +59,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.WriteLine(logstr.ToString()); Console.Error.Write(logstr.ToString());
break; break;
} }
} }

View file

@ -42,7 +42,7 @@ public class VM
// Send the new size to all users // Send the new size to all users
foreach (User user in this.Users.ToArray()) foreach (User user in this.Users.ToArray())
{ {
if (screenHidden && (user.Rank == Rank.Unregistered || user.Rank == Rank.Registered)) continue; if (screenHidden && user.Rank == Rank.Unregistered) continue;
user.SendScreenSize(e); user.SendScreenSize(e);
} }
} }
@ -68,7 +68,7 @@ public class VM
// Send the dirty rect to all users // Send the dirty rect to all users
foreach (User user in this.Users.ToArray()) foreach (User user in this.Users.ToArray())
{ {
if (screenHidden && (user.Rank == Rank.Unregistered || user.Rank == Rank.Registered)) continue; if (screenHidden && user.Rank == Rank.Unregistered) continue;
user.SendRect(jpg64, e.X, e.Y); user.SendRect(jpg64, e.X, e.Y);
} }
} }
@ -236,7 +236,7 @@ public class VM
if (hidden) if (hidden)
{ {
this.screenHidden = true; this.screenHidden = true;
foreach (User u in this.Users.Where(u => u.Rank == Rank.Unregistered || u.Rank == Rank.Registered)) foreach (User u in this.Users.Where(u => u.Rank == Rank.Unregistered))
{ {
await u.SendScreenSize(new Size(1024, 768)); await u.SendScreenSize(new Size(1024, 768));
await u.SendRect(Program.ScreenHiddenBase64, 0, 0); await u.SendRect(Program.ScreenHiddenBase64, 0, 0);
@ -245,7 +245,7 @@ public class VM
else else
{ {
this.screenHidden = false; this.screenHidden = false;
foreach (User u in this.Users.Where(u => u.Rank == Rank.Unregistered || u.Rank == Rank.Registered)) foreach (User u in this.Users.Where(u => u.Rank == Rank.Unregistered))
{ {
await u.SendScreenSize(GetScreenSize()); await u.SendScreenSize(GetScreenSize());
await u.SendRect(Convert.ToBase64String(await GetFramebufferJpeg()), 0, 0); await u.SendRect(Convert.ToBase64String(await GetFramebufferJpeg()), 0, 0);

View file

@ -244,9 +244,6 @@ public class QEMUController : VMController
private async Task StopQEMU() private async Task StopQEMU()
{ {
if (_expectedExit) return;
_qmpReconnectTimer.Stop();
_qemuRestartTimer?.Stop();
_expectedExit = true; _expectedExit = true;
if (this._qemuProc == null || this._qemuProc.HasExited) return; if (this._qemuProc == null || this._qemuProc.HasExited) return;
await this._vnc.Disconnect(); await this._vnc.Disconnect();
@ -275,7 +272,6 @@ public class QEMUController : VMController
public override async Task Reset() public override async Task Reset()
{ {
if (!Snapshots) return; if (!Snapshots) return;
if (_expectedExit) return;
await this.StopQEMU(); await this.StopQEMU();
await this.Start(); await this.Start();
} }

View file

@ -13,7 +13,7 @@
<PackageReference Include="CircularBuffer" Version="1.3.0" /> <PackageReference Include="CircularBuffer" Version="1.3.0" />
<PackageReference Include="MySqlConnector" Version="2.3.3" /> <PackageReference Include="MySqlConnector" Version="2.3.3" />
<PackageReference Include="Samboy063.Tomlet" Version="5.2.0" /> <PackageReference Include="Samboy063.Tomlet" Version="5.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup> </ItemGroup>

View file

@ -15,16 +15,6 @@ OriginCheck = false
# List of domains allowed to host webapps that connect to your VMs. # List of domains allowed to host webapps that connect to your VMs.
AllowedOrigins = ["https://computernewb.com", "http://localhost:3000"] AllowedOrigins = ["https://computernewb.com", "http://localhost:3000"]
[Auth]
Enabled = false
AuthServerURI = "http://127.0.0.1:5858"
SecretKey = "hunter2"
[Auth.GuestPermissions]
Chat = true
Turns = false
Vote = false
[Turns] [Turns]
# How long each turn is # How long each turn is
TurnTime = 20 TurnTime = 20