Compare commits
No commits in common. "master" and "master" have entirely different histories.
2
QMPSharp
2
QMPSharp
|
@ -1 +1 @@
|
|||
Subproject commit cd42387f6e4610341b15e0859f78f8a394914aae
|
||||
Subproject commit f0de933380c0c6488306d04b340f9a1599a4d217
|
|
@ -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; }
|
||||
}
|
|
@ -3,7 +3,6 @@ namespace CollabVM.Server.Config;
|
|||
public class IConfig
|
||||
{
|
||||
public HTTPConfig HTTP { get; set; }
|
||||
public AuthConfig Auth { get; set; }
|
||||
public TurnConfig Turns { get; set; }
|
||||
public VoteConfig Votes { get; set; }
|
||||
public ChatConfig Chat { get; set; }
|
||||
|
@ -120,18 +119,3 @@ public class QEMUVMConfig
|
|||
public string? QMPSocketDir { 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; }
|
||||
}
|
|
@ -17,7 +17,6 @@ class Program
|
|||
public static Random rnd = new();
|
||||
public static IConfig Config;
|
||||
public static Database? Database;
|
||||
public static AuthManager AuthManager;
|
||||
// These can go here for now, might move them later
|
||||
public static readonly string ScreenHiddenBase64 = Convert.ToBase64String(Utilities.GetAsset("screenhidden.jpeg"));
|
||||
public static readonly byte[] ScreenHiddenThumb = Utilities.GetAsset("screenhiddenthumb.jpeg");
|
||||
|
@ -57,12 +56,6 @@ class Program
|
|||
await Database.OpenAsync();
|
||||
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
|
||||
VMManager = new VMManager(Config.VMs);
|
||||
await VMManager.StartAll();
|
||||
|
|
|
@ -3,7 +3,6 @@ namespace CollabVM.Server;
|
|||
public enum Rank
|
||||
{
|
||||
Unregistered = 0,
|
||||
Registered = 1,
|
||||
Admin = 2,
|
||||
Moderator = 3,
|
||||
}
|
|
@ -64,7 +64,6 @@ public class User
|
|||
this.Disconnected += OnDisconnected;
|
||||
this.ConnectedToVM += delegate { };
|
||||
this.Renamed += delegate { };
|
||||
if (Program.Config.Auth.Enabled) SendAsync(Guacutils.Encode("auth", Program.Config.Auth.AuthServerURI!));
|
||||
SendAsync("3.nop;");
|
||||
ReadLoop(tokenSource.Token);
|
||||
}
|
||||
|
@ -123,25 +122,6 @@ public class User
|
|||
break;
|
||||
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)
|
||||
await this.AssignGuestUsername(this.vm);
|
||||
else
|
||||
|
@ -197,10 +177,10 @@ public class User
|
|||
chatmsg.Add(vm.Config.MOTD);
|
||||
await this.SendAsync(Guacutils.Encode(chatmsg.ToArray()));
|
||||
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());
|
||||
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.SendRect(Program.ScreenHiddenBase64, 0, 0);
|
||||
|
@ -212,52 +192,10 @@ public class User
|
|||
}
|
||||
}
|
||||
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":
|
||||
{
|
||||
if (this.vm == null || msgArr.Length != 2 || IPData!.MuteStatus != MuteStatus.None) return;
|
||||
if (Program.Config.Auth.Enabled && !Program.Config.Auth.GuestPermissions.Chat &&
|
||||
_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 (_rank == Rank.Unregistered && ChatLimiter != null && !ChatLimiter.Limit())
|
||||
{
|
||||
if (IPData!.MuteStatus == MuteStatus.None) await Mute(false);
|
||||
return;
|
||||
|
@ -303,12 +241,6 @@ public class User
|
|||
case "turn":
|
||||
{
|
||||
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))
|
||||
{
|
||||
vm.TurnQueue.AddUser(this);
|
||||
|
@ -321,12 +253,6 @@ public class User
|
|||
case "vote":
|
||||
{
|
||||
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)
|
||||
{
|
||||
await this.SendChat("", "This VM does not support voting to reset");
|
||||
|
@ -355,13 +281,6 @@ public class User
|
|||
{
|
||||
// Login
|
||||
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();
|
||||
var hash = Utilities.BytesToHex(sha.ComputeHash(Encoding.UTF8.GetBytes(msgArr[2])));
|
||||
if (hash == Program.Config.Staff.AdminPasswordHash)
|
||||
|
@ -495,11 +414,6 @@ public class User
|
|||
{
|
||||
// Rename user
|
||||
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]);
|
||||
if (user == null) return;
|
||||
await user.Rename(msgArr[3]);
|
||||
|
|
|
@ -59,7 +59,7 @@ public static class Utilities
|
|||
case LogLevel.WARN:
|
||||
case LogLevel.ERROR:
|
||||
case LogLevel.FATAL:
|
||||
Console.Error.WriteLine(logstr.ToString());
|
||||
Console.Error.Write(logstr.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class VM
|
|||
// Send the new size to all users
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class VM
|
|||
// Send the dirty rect to all users
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ public class VM
|
|||
if (hidden)
|
||||
{
|
||||
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.SendRect(Program.ScreenHiddenBase64, 0, 0);
|
||||
|
@ -245,7 +245,7 @@ public class VM
|
|||
else
|
||||
{
|
||||
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.SendRect(Convert.ToBase64String(await GetFramebufferJpeg()), 0, 0);
|
||||
|
|
|
@ -244,9 +244,6 @@ public class QEMUController : VMController
|
|||
|
||||
private async Task StopQEMU()
|
||||
{
|
||||
if (_expectedExit) return;
|
||||
_qmpReconnectTimer.Stop();
|
||||
_qemuRestartTimer?.Stop();
|
||||
_expectedExit = true;
|
||||
if (this._qemuProc == null || this._qemuProc.HasExited) return;
|
||||
await this._vnc.Disconnect();
|
||||
|
@ -275,7 +272,6 @@ public class QEMUController : VMController
|
|||
public override async Task Reset()
|
||||
{
|
||||
if (!Snapshots) return;
|
||||
if (_expectedExit) return;
|
||||
await this.StopQEMU();
|
||||
await this.Start();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<PackageReference Include="CircularBuffer" Version="1.3.0" />
|
||||
<PackageReference Include="MySqlConnector" Version="2.3.3" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -15,16 +15,6 @@ OriginCheck = false
|
|||
# List of domains allowed to host webapps that connect to your VMs.
|
||||
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]
|
||||
# How long each turn is
|
||||
TurnTime = 20
|
||||
|
|
Loading…
Reference in a new issue