1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2024-10-18 15:42:50 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
0a29b6f21e
WIP 2023-10-15 17:10:53 +02:00
8bee9bc763
WIP 2023-10-15 17:10:39 +02:00
22 changed files with 169 additions and 40 deletions

24
.run/Web.run.xml Normal file
View File

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Web" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/.artifacts/bin/Phantom.Web/debug/Phantom.Web.exe" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Web" />
<option name="PASS_PARENT_ENVS" value="1" />
<envs>
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
<env name="WEB_SERVER_HOST" value="localhost" />
</envs>
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Web/Phantom.Web/Phantom.Web.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="0" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net8.0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>

View File

@ -4,7 +4,7 @@ using Phantom.Controller.Database.Entities;
namespace Phantom.Controller.Services.Users; namespace Phantom.Controller.Services.Users;
internal static class UserPasswords { static class UserPasswords {
private static PasswordHasher<UserEntity> Hasher { get; } = new (); private static PasswordHasher<UserEntity> Hasher { get; } = new ();
private const int MinimumLength = 16; private const int MinimumLength = 16;

View File

@ -17,6 +17,34 @@ public static class Files {
await stream.WriteAsync(bytes); await stream.WriteAsync(bytes);
} }
public static async Task ReadExactlyBytesAsync(string path, Memory<byte> bytes) {
var options = new FileStreamOptions {
Mode = FileMode.Open,
Access = FileAccess.Read,
Options = FileOptions.Asynchronous,
Share = FileShare.Read
};
await using var stream = new FileStream(path, options);
bool wrongLength = false;
if (stream.Length == bytes.Length) {
try {
await stream.ReadExactlyAsync(bytes);
} catch (EndOfStreamException) {
wrongLength = true;
}
}
else {
wrongLength = true;
}
if (wrongLength) {
throw new IOException("Expected file size to be exactly " + bytes.Length + " B, actual size is " + stream.Length + " B.");
}
}
public static void RequireMaximumFileSize(string path, long maximumBytes) { public static void RequireMaximumFileSize(string path, long maximumBytes) {
var actualBytes = new FileInfo(path).Length; var actualBytes = new FileInfo(path).Length;
if (actualBytes > maximumBytes) { if (actualBytes > maximumBytes) {

View File

@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Controller.Services.Users;
using Phantom.Utils.Cryptography; using Phantom.Utils.Cryptography;
using Phantom.Web.Identity.Interfaces; using Phantom.Web.Identity.Interfaces;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Phantom.Web.Identity.Data;
namespace Phantom.Web.Identity.Authorization; namespace Phantom.Web.Identity.Authorization;

View File

@ -1,6 +1,4 @@
using Phantom.Controller.Database.Entities; namespace Phantom.Web.Identity.Interfaces;
namespace Phantom.Web.Identity.Interfaces;
public interface ILoginEvents { public interface ILoginEvents {
void UserLoggedIn(UserEntity user); void UserLoggedIn(UserEntity user);

View File

@ -1,6 +1,4 @@
using Phantom.Controller.Database.Entities; using Phantom.Web.Identity.Interfaces;
using Phantom.Controller.Services.Audit;
using Phantom.Web.Identity.Interfaces;
namespace Phantom.Web.Base; namespace Phantom.Web.Base;

View File

@ -1,8 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Web.Identity.Authorization;
using Phantom.Web.Identity.Data;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace Phantom.Web.Base; namespace Phantom.Web.Base;

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Phantom.Controller.Services;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
using Phantom.Web.Base; using Phantom.Web.Base;
using Phantom.Web.Identity; using Phantom.Web.Identity;

View File

@ -1,5 +1,4 @@
@using Phantom.Controller.Services @inject ServiceConfiguration Configuration
@inject ServiceConfiguration Configuration
@inject PermissionManager PermissionManager @inject PermissionManager PermissionManager
<div class="navbar navbar-dark"> <div class="navbar navbar-dark">

View File

@ -1,5 +1,4 @@
@page "/agents" @page "/agents"
@using Phantom.Controller.Services.Agents
@using Phantom.Utils.Collections @using Phantom.Utils.Collections
@implements IDisposable @implements IDisposable
@inject AgentManager AgentManager @inject AgentManager AgentManager

View File

@ -1,10 +1,6 @@
@page "/audit" @page "/audit"
@attribute [Authorize(Permission.ViewAuditPolicy)] @attribute [Authorize(Permission.ViewAuditPolicy)]
@using Phantom.Controller.Services.Audit
@using Phantom.Controller.Services.Instances
@using Phantom.Controller.Services.Users
@using System.Collections.Immutable @using System.Collections.Immutable
@using Phantom.Controller.Database.Enums
@implements IDisposable @implements IDisposable
@inject AuditLog AuditLog @inject AuditLog AuditLog
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,10 +1,6 @@
@page "/events" @page "/events"
@attribute [Authorize(Permission.ViewEventsPolicy)] @attribute [Authorize(Permission.ViewEventsPolicy)]
@using System.Collections.Immutable @using System.Collections.Immutable
@using Phantom.Controller.Services.Events
@using Phantom.Controller.Services.Instances
@using Phantom.Controller.Database.Enums
@using Phantom.Controller.Services.Agents
@implements IDisposable @implements IDisposable
@inject AgentManager AgentManager @inject AgentManager AgentManager
@inject EventLog EventLog @inject EventLog EventLog

View File

@ -1,10 +1,6 @@
@page "/instances/{InstanceGuid:guid}" @page "/instances/{InstanceGuid:guid}"
@attribute [Authorize(Permission.ViewInstancesPolicy)] @attribute [Authorize(Permission.ViewInstancesPolicy)]
@inherits PhantomComponent @inherits PhantomComponent
@using Phantom.Common.Data.Instance
@using Phantom.Common.Data.Replies
@using Phantom.Controller.Services.Audit
@using Phantom.Controller.Services.Instances
@implements IDisposable @implements IDisposable
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager
@inject AuditLog AuditLog @inject AuditLog AuditLog

View File

@ -1,7 +1,5 @@
@page "/instances/{InstanceGuid:guid}/edit" @page "/instances/{InstanceGuid:guid}/edit"
@attribute [Authorize(Permission.CreateInstancesPolicy)] @attribute [Authorize(Permission.CreateInstancesPolicy)]
@using Phantom.Common.Data.Instance
@using Phantom.Controller.Services.Instances
@inherits PhantomComponent @inherits PhantomComponent
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,8 +1,6 @@
@page "/instances" @page "/instances"
@attribute [Authorize(Permission.ViewInstancesPolicy)] @attribute [Authorize(Permission.ViewInstancesPolicy)]
@using System.Collections.Immutable @using System.Collections.Immutable
@using Phantom.Controller.Services.Instances
@using Phantom.Controller.Services.Agents
@implements IDisposable @implements IDisposable
@inject AgentManager AgentManager @inject AgentManager AgentManager
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,12 +1,8 @@
@page "/setup" @page "/setup"
@using Phantom.Controller.Services.Users
@using Phantom.Utils.Cryptography
@using Phantom.Utils.Tasks @using Phantom.Utils.Tasks
@using Phantom.Controller.Database.Entities
@using Phantom.Controller.Services
@using Phantom.Controller.Services.Audit
@using Phantom.Web.Identity.Authentication @using Phantom.Web.Identity.Authentication
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using Phantom.Utils.Cryptography
@using System.Security.Cryptography @using System.Security.Cryptography
@attribute [AllowAnonymous] @attribute [AllowAnonymous]
@inject ServiceConfiguration ServiceConfiguration @inject ServiceConfiguration ServiceConfiguration

View File

@ -1,6 +1,4 @@
@page "/users" @page "/users"
@using Phantom.Controller.Database.Entities
@using Phantom.Controller.Services.Users
@using System.Collections.Immutable @using System.Collections.Immutable
@attribute [Authorize(Permission.ViewUsersPolicy)] @attribute [Authorize(Permission.ViewUsersPolicy)]
@inject UserManager UserManager @inject UserManager UserManager

View File

@ -0,0 +1,68 @@
using System.Reflection;
using Phantom.Common.Logging;
using Phantom.Utils.Cryptography;
using Phantom.Utils.IO;
using Phantom.Utils.Runtime;
using Phantom.Utils.Tasks;
using Phantom.Web;
var cancellationTokenSource = new CancellationTokenSource();
PosixSignals.RegisterCancellation(cancellationTokenSource, static () => {
PhantomLogger.Root.InformationHeading("Stopping Phantom Panel web...");
});
static void CreateFolderOrStop(string path, UnixFileMode chmod) {
if (!Directory.Exists(path)) {
try {
Directories.Create(path, chmod);
} catch (Exception e) {
PhantomLogger.Root.Fatal(e, "Error creating folder: {FolderName}", path);
throw StopProcedureException.Instance;
}
}
}
try {
var fullVersion = AssemblyAttributes.GetFullVersion(Assembly.GetExecutingAssembly());
PhantomLogger.Root.InformationHeading("Initializing Phantom Panel web...");
PhantomLogger.Root.Information("Web version: {Version}", fullVersion);
var (controllerHost, controllerPort, webKeyToken, webKeyFilePath, webServerHost, webServerPort, webBasePath) = Variables.LoadOrStop();
string webKeysPath = Path.GetFullPath("./keys");
CreateFolderOrStop(webKeysPath, Chmod.URWX);
PhantomLogger.Root.InformationHeading("Launching Phantom Panel web...");
var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Web"));
try {
var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, webKeysPath, cancellationTokenSource.Token);
var administratorToken = TokenGenerator.Create(60);
PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken);
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", configuration.HttpUrl, configuration.BasePath + "setup");
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), cancellationTokenSource.Token);
var webApplication = Launcher.CreateApplication(configuration, serviceConfiguration, taskManager);
await Launcher.Launch(configuration, webApplication);
} finally {
cancellationTokenSource.Cancel();
await taskManager.Stop();
}
return 0;
} catch (OperationCanceledException) {
return 0;
} catch (StopProcedureException) {
return 1;
} catch (Exception e) {
PhantomLogger.Root.Fatal(e, "Caught exception in entry point.");
return 1;
} finally {
cancellationTokenSource.Dispose();
PhantomLogger.Root.Information("Bye!");
PhantomLogger.Dispose();
}

View File

@ -0,0 +1,7 @@
namespace Phantom.Web;
public sealed record ServiceConfiguration(
string Version,
byte[] AdministratorToken,
CancellationToken CancellationToken
);

View File

@ -1,9 +1,7 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Phantom.Controller.Database.Entities;
using Phantom.Web.Base; using Phantom.Web.Base;
using Phantom.Web.Components.Forms; using Phantom.Web.Components.Forms;
using Phantom.Web.Identity.Data;
namespace Phantom.Web.Shared; namespace Phantom.Web.Shared;

View File

@ -0,0 +1,37 @@
using Phantom.Common.Logging;
using Phantom.Utils.Runtime;
namespace Phantom.Web;
sealed record Variables(
string ControllerHost,
ushort ControllerPort,
string? WebKeyToken,
string? WebKeyFilePath,
string WebServerHost,
ushort WebServerPort,
string WebBasePath
) {
private static Variables LoadOrThrow() {
var (webKeyToken, webKeyFilePath) = EnvironmentVariables.GetEitherString("WEB_KEY", "WEB_KEY_FILE").Require;
return new Variables(
EnvironmentVariables.GetString("CONTROLLER_HOST").Require,
EnvironmentVariables.GetPortNumber("CONTROLLER_PORT").WithDefault(9402),
webKeyToken,
webKeyFilePath,
EnvironmentVariables.GetString("WEB_SERVER_HOST").WithDefault("0.0.0.0"),
EnvironmentVariables.GetPortNumber("WEB_SERVER_PORT").WithDefault(9400),
EnvironmentVariables.GetString("WEB_BASE_PATH").Validate(static value => value.StartsWith('/') && value.EndsWith('/'), "Environment variable must begin and end with '/'").WithDefault("/")
);
}
public static Variables LoadOrStop() {
try {
return LoadOrThrow();
} catch (Exception e) {
PhantomLogger.Root.Fatal(e.Message);
throw StopProcedureException.Instance;
}
}
}