33 Commits
v1b8 ... v1b10

Author SHA1 Message Date
Marcel Baumgartner
a8bd1193ce Merge pull request #195 from Moonlight-Panel/SecurityPatches
Security patches
2023-06-26 00:10:03 +02:00
Marcel Baumgartner
366d1a9205 Merge pull request #194 from Moonlight-Panel/AddStreamerModeAndFixUI
Added streamer mode and fixed security settings ui
2023-06-26 00:07:23 +02:00
Marcel Baumgartner
df9ed95c6b Added streamer mode and fixed security settings ui 2023-06-26 00:06:44 +02:00
Marcel Baumgartner
23a211362e Merge pull request #193 from Moonlight-Panel/EnhanceFileManager
Enhanced winscp button and new file/folder menu
2023-06-25 17:32:57 +02:00
Marcel Baumgartner
cf91d44902 Enhanced winscp button and new file/folder menu 2023-06-25 17:31:36 +02:00
Marcel Baumgartner
35633e21a9 Merge pull request #192 from Moonlight-Panel/EnhanceServerListLayout
Enhanced server list
2023-06-25 00:01:53 +02:00
Marcel Baumgartner
ce8b8f6798 Enhanced server list 2023-06-25 00:01:28 +02:00
Marcel Baumgartner
c28c80ba25 Merge pull request #191 from Moonlight-Panel/FixDnsManager
Fixed dns loading issues. Added udp support
2023-06-24 23:45:49 +02:00
Marcel Baumgartner
da17b1df93 Fixed dns loading issues. Added udp support 2023-06-24 23:45:29 +02:00
Marcel Baumgartner
f9f5865ef9 Prevent user locking when duplicating the email entries 2023-06-24 22:35:38 +02:00
Marcel Baumgartner
389ded9b77 Fixed oauth2 account spoofing using unverified discord accounts for claiming identity 2023-06-24 22:15:04 +02:00
Marcel Baumgartner
faebaa59dd Merge pull request #189 from Moonlight-Panel/AddCustomLayoutServerList
Added custom layout options for the server list
2023-06-24 02:35:21 +02:00
Marcel Baumgartner
6b7dc2ad05 Added custom layout options for the server list 2023-06-24 02:35:01 +02:00
Marcel Baumgartner
e356c9d0c8 Update README.md 2023-06-23 05:06:12 +02:00
Marcel Baumgartner
efed2a6a5c Merge pull request #188 from Moonlight-Panel/ImproveLogging
Improved logging. Added better error handling for mysql database backup
2023-06-23 00:51:32 +02:00
Marcel Baumgartner
6f138c2c51 Improved logging. Added better error handling for mysql database backup 2023-06-23 00:51:09 +02:00
Marcel Baumgartner
6c43e6a533 Merge pull request #187 from Moonlight-Panel/AddMalwareScan
Added maleware scan
2023-06-22 20:36:58 +02:00
Marcel Baumgartner
0379afd831 Added maleware scan 2023-06-22 20:36:33 +02:00
Marcel Baumgartner
b8bfdb7729 Merge pull request #185 from Moonlight-Panel/SwitchToSerilog
Switched to serilog as logging system
2023-06-21 19:15:53 +02:00
Marcel Baumgartner
72f60ec97c Switched to serilog as logging system 2023-06-21 19:15:30 +02:00
Marcel Baumgartner
1b40250750 Revert "Merge branch 'DiscordBot' into main"
This reverts commit cdf2988cb6, reversing
changes made to 76415b4a0a.
2023-06-20 20:59:49 +02:00
Ole Sziedat
cdf2988cb6 Merge branch 'DiscordBot' into main 2023-06-20 20:52:46 +02:00
Marcel Baumgartner
76415b4a0a Merge pull request #182 from Moonlight-Panel/ImproveFileEditor
Added STRG+S support for editor and better saving
2023-06-20 20:38:17 +02:00
Marcel Baumgartner
52d00baf2b Added STRG+S support for editor and better saving 2023-06-20 20:37:57 +02:00
Marcel Baumgartner
cd41db510e Merge pull request #181 from Moonlight-Panel/AddServerAllocationEditor
Added a basic allocation editor for servers
2023-06-20 19:21:41 +02:00
Marcel Baumgartner
1afd4e8b92 Added a basic allocation editor for servers 2023-06-20 19:18:18 +02:00
Marcel Baumgartner
ef37088c7a Merge pull request #180 from Moonlight-Panel/ImproveUserInterface
Fixed ui bugs, improved plugins page, added new 404 component
2023-06-20 02:56:15 +02:00
Marcel Baumgartner
e71495533b Fixed ui bugs, improved plugins page, added new 404 component 2023-06-20 02:55:50 +02:00
Marcel Baumgartner
e2a6d70f6a Update README.md 2023-06-18 06:23:17 +02:00
Marcel Baumgartner
e95853b09c Update README.md 2023-06-18 03:25:23 +02:00
Marcel Baumgartner
0537ca115e Remove assets from github language statistics 2023-06-18 02:50:08 +02:00
0fde9a5005 bot hotfix 2023-05-04 20:40:34 +02:00
Marcel Baumgartner
74541d7f87 Merge pull request #107 from Moonlight-Panel/main
Update to upstream branch
2023-04-29 23:39:46 +02:00
108 changed files with 4067 additions and 1503 deletions

1
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto
Moonlight/wwwroot/* linguist-vendored

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Newtonsoft.Json;
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.ApiClients.Modrinth;

View File

@@ -23,7 +23,9 @@ public class User
public string State { get; set; } = "";
public string Country { get; set; } = "";
public string ServerListLayoutJson { get; set; } = "";
// States
public UserStatus Status { get; set; } = UserStatus.Unverified;
@@ -31,6 +33,7 @@ public class User
public bool SupportPending { get; set; } = false;
public bool HasRated { get; set; } = false;
public int Rating { get; set; } = 0;
public bool StreamerMode { get; set; } = false;
// Security
public bool TotpEnabled { get; set; } = false;

View File

@@ -1,6 +1,6 @@
using System.Data.Common;
using Logging.Net;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Moonlight.App.Helpers;
namespace Moonlight.App.Database.Interceptors;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedServerListLayoutToUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ServerListLayoutJson",
table: "Users",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ServerListLayoutJson",
table: "Users");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedStreamerMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "StreamerMode",
table: "Users",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "StreamerMode",
table: "Users");
}
}
}

View File

@@ -780,6 +780,10 @@ namespace Moonlight.App.Database.Migrations
b.Property<int>("Rating")
.HasColumnType("int");
b.Property<string>("ServerListLayoutJson")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State")
.IsRequired()
.HasColumnType("longtext");
@@ -787,6 +791,9 @@ namespace Moonlight.App.Database.Migrations
b.Property<int>("Status")
.HasColumnType("int");
b.Property<bool>("StreamerMode")
.HasColumnType("tinyint(1)");
b.Property<int>("SubscriptionDuration")
.HasColumnType("int");

View File

@@ -1,5 +1,5 @@
using System.Diagnostics;
using Logging.Net;
using Moonlight.App.Helpers;
namespace Moonlight.App.Events;

View File

@@ -1,252 +0,0 @@
using System.Diagnostics;
using Logging.Net;
using Logging.Net.Loggers.SB;
using Moonlight.App.Models.Misc;
using ILogger = Logging.Net.ILogger;
namespace Moonlight.App.Helpers;
public class CacheLogger : ILogger
{
private SBLogger SbLogger = new();
private List<LogEntry> Messages = new();
public LogEntry[] GetMessages()
{
lock (Messages)
{
var result = new LogEntry[Messages.Count];
Messages.CopyTo(result);
return result;
}
}
public void Clear(int messages)
{
lock (Messages)
{
Messages.RemoveRange(0, Math.Min(messages, Messages.Count));
}
}
public void Info(string? s)
{
if (s == null)
return;
lock (Messages)
{
Messages.Add(new()
{
Level = "info",
Message = s
});
}
SbLogger.Info(s);
}
public void Debug(string? s)
{
if (s == null)
return;
lock (Messages)
{
Messages.Add(new()
{
Level = "debug",
Message = s
});
}
SbLogger.Debug(s);
}
public void Warn(string? s)
{
if (s == null)
return;
lock (Messages)
{
Messages.Add(new()
{
Level = "warn",
Message = s
});
}
SbLogger.Warn(s);
}
public void Error(string? s)
{
if (s == null)
return;
lock (Messages)
{
Messages.Add(new()
{
Level = "error",
Message = s
});
}
SbLogger.Error(s);
}
public void Fatal(string? s)
{
if (s == null)
return;
lock (Messages)
{
Messages.Add(new()
{
Level = "fatal",
Message = s
});
}
SbLogger.Fatal(s);
}
public void InfoEx(Exception ex)
{
lock (Messages)
{
Messages.Add(new()
{
Level = "info",
Message = ex.ToStringDemystified()
});
}
SbLogger.InfoEx(ex);
}
public void DebugEx(Exception ex)
{
lock (Messages)
{
Messages.Add(new()
{
Level = "debug",
Message = ex.ToStringDemystified()
});
}
SbLogger.DebugEx(ex);
}
public void WarnEx(Exception ex)
{
lock (Messages)
{
Messages.Add(new()
{
Level = "warn",
Message = ex.ToStringDemystified()
});
}
SbLogger.WarnEx(ex);
}
public void ErrorEx(Exception ex)
{
lock (Messages)
{
Messages.Add(new()
{
Level = "error",
Message = ex.ToStringDemystified()
});
}
SbLogger.ErrorEx(ex);
}
public void FatalEx(Exception ex)
{
lock (Messages)
{
Messages.Add(new()
{
Level = "fatal",
Message = ex.ToStringDemystified()
});
}
SbLogger.FatalEx(ex);
}
public LoggingConfiguration GetErrorConfiguration()
{
return SbLogger.GetErrorConfiguration();
}
public void SetErrorConfiguration(LoggingConfiguration configuration)
{
SbLogger.SetErrorConfiguration(configuration);
}
public LoggingConfiguration GetFatalConfiguration()
{
return SbLogger.GetFatalConfiguration();
}
public void SetFatalConfiguration(LoggingConfiguration configuration)
{
SbLogger.SetFatalConfiguration(configuration);
}
public LoggingConfiguration GetWarnConfiguration()
{
return SbLogger.GetWarnConfiguration();
}
public void SetWarnConfiguration(LoggingConfiguration configuration)
{
SbLogger.SetWarnConfiguration(configuration);
}
public LoggingConfiguration GetInfoConfiguration()
{
return SbLogger.GetInfoConfiguration();
}
public void SetInfoConfiguration(LoggingConfiguration configuration)
{
SbLogger.SetInfoConfiguration(configuration);
}
public LoggingConfiguration GetDebugConfiguration()
{
return SbLogger.GetDebugConfiguration();
}
public void SetDebugConfiguration(LoggingConfiguration configuration)
{
SbLogger.SetDebugConfiguration(configuration);
}
public ILoggingAddition GetAddition()
{
return SbLogger.GetAddition();
}
public void SetAddition(ILoggingAddition addition)
{
SbLogger.SetAddition(addition);
}
public bool LogCallingClass
{
get => SbLogger.LogCallingClass;
set => SbLogger.LogCallingClass = value;
}
}

View File

@@ -1,5 +1,4 @@
using System.Diagnostics;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
using Moonlight.App.Services;
@@ -82,19 +81,32 @@ public class DatabaseCheckupService
Logger.Info($"Saving it to: {file}");
Logger.Info("Starting backup...");
var sw = new Stopwatch();
sw.Start();
try
{
var sw = new Stopwatch();
sw.Start();
await using MySqlConnection conn = new MySqlConnection(connectionString);
await using MySqlCommand cmd = new MySqlCommand();
using MySqlBackup mb = new MySqlBackup(cmd);
await using MySqlConnection conn = new MySqlConnection(connectionString);
await using MySqlCommand cmd = new MySqlCommand();
using MySqlBackup mb = new MySqlBackup(cmd);
cmd.Connection = conn;
await conn.OpenAsync();
mb.ExportToFile(file);
await conn.CloseAsync();
cmd.Connection = conn;
await conn.OpenAsync();
mb.ExportToFile(file);
await conn.CloseAsync();
sw.Stop();
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");
sw.Stop();
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");
}
catch (Exception e)
{
Logger.Fatal("-----------------------------------------------");
Logger.Fatal("Unable to create backup for moonlight database");
Logger.Fatal("Moonlight will start anyways in 30 seconds");
Logger.Fatal("-----------------------------------------------");
Logger.Fatal(e);
Thread.Sleep(TimeSpan.FromSeconds(30));
}
}
}

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Renci.SshNet;
using Renci.SshNet;
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
namespace Moonlight.App.Helpers.Files;

View File

@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Logging.Net;
namespace Moonlight.App.Helpers;

View File

@@ -0,0 +1,108 @@
using System.Diagnostics;
using System.Reflection;
using Serilog;
namespace Moonlight.App.Helpers;
public static class Logger
{
#region String method calls
public static void Verbose(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Verbose("{Message}", message);
}
public static void Info(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Information("{Message}", message);
}
public static void Debug(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Debug("{Message}", message);
}
public static void Error(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Error("{Message}", message);
}
public static void Warn(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Warning("{Message}", message);
}
public static void Fatal(string message, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Fatal("{Message}", message);
}
#endregion
#region Exception method calls
public static void Verbose(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Verbose(exception, "");
}
public static void Info(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Information(exception, "");
}
public static void Debug(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Debug(exception, "");
}
public static void Error(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Error(exception, "");
}
public static void Warn(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Warning(exception, "");
}
public static void Fatal(Exception exception, string channel = "default")
{
Log.ForContext("SourceContext", GetNameOfCallingClass())
.Fatal(exception, "");
}
#endregion
private static string GetNameOfCallingClass(int skipFrames = 4)
{
string fullName;
Type declaringType;
do
{
MethodBase method = new StackFrame(skipFrames, false).GetMethod();
declaringType = method.DeclaringType;
if (declaringType == null)
{
return method.Name;
}
skipFrames++;
if (declaringType.Name.Contains("<"))
fullName = declaringType.ReflectedType.Name;
else
fullName = declaringType.Name;
}
while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase) | fullName.Contains("Logger"));
return fullName;
}
}

View File

@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using Logging.Net;
namespace Moonlight.App.Helpers;

View File

@@ -1,6 +1,4 @@
using Logging.Net;
namespace Moonlight.App.Helpers;
namespace Moonlight.App.Helpers;
public static class ParseHelper
{

View File

@@ -1,6 +1,5 @@
using System.Net.WebSockets;
using System.Text;
using Logging.Net;
using Moonlight.App.Helpers.Wings.Data;
using Moonlight.App.Helpers.Wings.Enums;
using Moonlight.App.Helpers.Wings.Events;

View File

@@ -1,6 +1,5 @@
using System.Net.WebSockets;
using System.Text;
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
@@ -9,7 +8,6 @@ using Moonlight.App.Models.Notifications;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.Sessions;
using Newtonsoft.Json;
namespace Moonlight.App.Http.Controllers.Api.Moonlight.Notifications;

View File

@@ -1,5 +1,5 @@
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Helpers;
using Moonlight.App.Services;
using Moonlight.App.Services.Sessions;

View File

@@ -1,12 +1,6 @@
using System.Text;
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Services;
using Moonlight.App.Services.Files;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
@@ -14,13 +8,10 @@ namespace Moonlight.App.Http.Controllers.Api.Moonlight;
[Route("api/moonlight/resources")]
public class ResourcesController : Controller
{
private readonly SecurityLogService SecurityLogService;
private readonly BucketService BucketService;
public ResourcesController(SecurityLogService securityLogService,
BucketService bucketService)
public ResourcesController(BucketService bucketService)
{
SecurityLogService = securityLogService;
BucketService = bucketService;
}
@@ -29,10 +20,7 @@ public class ResourcesController : Controller
{
if (name.Contains(".."))
{
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
{
x.Add<string>(name);
});
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
return NotFound();
}
@@ -52,10 +40,7 @@ public class ResourcesController : Controller
{
if (name.Contains(".."))
{
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
{
x.Add<string>(name);
});
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
return NotFound();
}
@@ -75,10 +60,7 @@ public class ResourcesController : Controller
{
if (name.Contains(".."))
{
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
{
x.Add<string>(name);
});
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
return NotFound();
}

View File

@@ -1,10 +1,8 @@
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Http.Requests.Daemon;
using Moonlight.App.Repositories;
using Moonlight.App.Services;
namespace Moonlight.App.Http.Controllers.Api.Remote;

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Moonlight.App.Helpers;
namespace Moonlight.App.LogMigrator;

View File

@@ -0,0 +1,6 @@
namespace Moonlight.App.Models.Forms;
public class UserPreferencesDataModel
{
public bool StreamerMode { get; set; } = false;
}

View File

@@ -0,0 +1,8 @@
namespace Moonlight.App.Models.Misc;
public class MalwareScanResult
{
public string Title { get; set; } = "";
public string Description { get; set; } = "";
public string Author { get; set; } = "";
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.App.Models.Misc;
public class ServerGroup
{
public string Name { get; set; } = "";
public List<string> Servers { get; set; } = new();
}

View File

@@ -1,5 +1,4 @@
using System.Text;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
@@ -87,6 +86,13 @@ public class DiscordOAuth2Provider : OAuth2Provider
var email = getData.GetValue<string>("email");
var id = getData.GetValue<ulong>("id");
var verified = getData.GetValue<bool>("verified");
if (!verified)
{
Logger.Warn("A user tried to use an unverified discord account to login", "security");
throw new DisplayException("You can only use verified discord accounts for oauth signin");
}
// Handle data

View File

@@ -1,5 +1,4 @@
using System.Text;
using Logging.Net;
using Moonlight.App.ApiClients.Google.Requests;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MineStatLib;
using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings;

View File

@@ -1,8 +1,8 @@
using Discord;
using Discord.Webhook;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Helpers;
using Moonlight.App.Services.Files;
namespace Moonlight.App.Services.Background;

View File

@@ -0,0 +1,196 @@
using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
namespace Moonlight.App.Services.Background;
public class MalwareScanService
{
private Repository<Server> ServerRepository;
private Repository<Node> NodeRepository;
private NodeService NodeService;
private ServerService ServerService;
private readonly EventSystem Event;
private readonly IServiceScopeFactory ServiceScopeFactory;
public bool IsRunning { get; private set; }
public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
public string Status { get; private set; } = "N/A";
public MalwareScanService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
{
ServiceScopeFactory = serviceScopeFactory;
Event = eventSystem;
ScanResults = new();
}
public Task Start()
{
if (IsRunning)
throw new DisplayException("Malware scan is already running");
Task.Run(Run);
return Task.CompletedTask;
}
private async Task Run()
{
IsRunning = true;
Status = "Clearing last results";
await Event.Emit("malwareScan.status", IsRunning);
lock (ScanResults)
{
ScanResults.Clear();
}
await Event.Emit("malwareScan.result");
using var scope = ServiceScopeFactory.CreateScope();
// Load services from di scope
NodeRepository = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
ServerRepository = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
NodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
ServerService = scope.ServiceProvider.GetRequiredService<ServerService>();
var nodes = NodeRepository.Get().ToArray();
var containers = new List<Container>();
// Fetch and summarize all running containers from all nodes
Logger.Verbose("Fetching and summarizing all running containers from all nodes");
Status = "Fetching and summarizing all running containers from all nodes";
await Event.Emit("malwareScan.status", IsRunning);
foreach (var node in nodes)
{
var metrics = await NodeService.GetDockerMetrics(node);
foreach (var container in metrics.Containers)
{
containers.Add(container);
}
}
var containerServerMapped = new Dictionary<Server, Container>();
// Map all the containers to their corresponding server if existing
Logger.Verbose("Mapping all the containers to their corresponding server if existing");
Status = "Mapping all the containers to their corresponding server if existing";
await Event.Emit("malwareScan.status", IsRunning);
foreach (var container in containers)
{
if (Guid.TryParse(container.Name, out Guid uuid))
{
var server = ServerRepository
.Get()
.FirstOrDefault(x => x.Uuid == uuid);
if(server == null)
continue;
containerServerMapped.Add(server, container);
}
}
// Perform scan
var resultsMapped = new Dictionary<Server, MalwareScanResult[]>();
foreach (var mapping in containerServerMapped)
{
Logger.Verbose($"Scanning server {mapping.Key.Name} for malware");
Status = $"Scanning server {mapping.Key.Name} for malware";
await Event.Emit("malwareScan.status", IsRunning);
var results = await PerformScanOnServer(mapping.Key, mapping.Value);
if (results.Any())
{
resultsMapped.Add(mapping.Key, results);
Logger.Verbose($"{results.Length} findings on server {mapping.Key.Name}");
}
}
Logger.Verbose($"Scan complete. Detected {resultsMapped.Count} servers with findings");
IsRunning = false;
Status = $"Scan complete. Detected {resultsMapped.Count} servers with findings";
await Event.Emit("malwareScan.status", IsRunning);
lock (ScanResults)
{
foreach (var mapping in resultsMapped)
{
ScanResults.Add(mapping.Key, mapping.Value);
}
}
await Event.Emit("malwareScan.result");
}
private async Task<MalwareScanResult[]> PerformScanOnServer(Server server, Container container)
{
var results = new List<MalwareScanResult>();
// TODO: Move scans to an universal format / api
// Define scans here
async Task ScanSelfBot()
{
var access = await ServerService.CreateFileAccess(server, null!);
var fileElements = await access.Ls();
if (fileElements.Any(x => x.Name == "tokens.txt"))
{
results.Add(new ()
{
Title = "Found SelfBot",
Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
Author = "Marcel Baumgartner"
});
}
}
async Task ScanFakePlayerPlugins()
{
var access = await ServerService.CreateFileAccess(server, null!);
var fileElements = await access.Ls();
if (fileElements.Any(x => !x.IsFile && x.Name == "plugins")) // Check for plugins folder
{
await access.Cd("plugins");
fileElements = await access.Ls();
foreach (var fileElement in fileElements)
{
if (fileElement.Name.ToLower().Contains("fake"))
{
results.Add(new()
{
Title = "Fake player plugin",
Description = $"Suspicious plugin file: {fileElement.Name}",
Author = "Marcel Baumgartner"
});
}
}
}
}
// Execute scans
await ScanSelfBot();
await ScanFakePlayerPlugins();
return results.ToArray();
}
}

View File

@@ -1,5 +1,4 @@
using System.Text;
using Logging.Net;
using Microsoft.Extensions.Primitives;
using Moonlight.App.Helpers;
using Moonlight.App.Services.Files;
@@ -42,8 +41,6 @@ public class ConfigService : IConfiguration
public void Reload()
{
Logger.Info($"Reading config from '{PathBuilder.File("storage", "configs", "config.json")}'");
Configuration = new ConfigurationBuilder().AddJsonStream(
new MemoryStream(Encoding.ASCII.GetBytes(
File.ReadAllText(
@@ -51,8 +48,6 @@ public class ConfigService : IConfiguration
)
)
)).Build();
Logger.Info("Reloaded configuration file");
}
public IEnumerable<IConfigurationSection> GetChildren()

View File

@@ -1,6 +1,5 @@
using Discord;
using Discord.WebSocket;
using Logging.Net;
namespace Moonlight.App.Services.DiscordBot.Commands;

View File

@@ -1,6 +1,5 @@
using Discord;
using Discord.WebSocket;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;

View File

@@ -1,8 +1,7 @@
using System.Diagnostics;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Logging.Net;
using Moonlight.App.Helpers;
using Moonlight.App.Services.DiscordBot.Commands;
using Moonlight.App.Services.DiscordBot.Modules;

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
using Discord.WebSocket;
using Logging.Net;
using Moonlight.App.Helpers;
namespace Moonlight.App.Services.DiscordBot.Modules;

View File

@@ -1,8 +1,8 @@
using Discord;
using Discord.WebSocket;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;

View File

@@ -1,18 +1,19 @@
using CloudFlare.Client;
using CloudFlare.Client.Api.Authentication;
using CloudFlare.Client.Api.Display;
using CloudFlare.Client.Api.Parameters.Data;
using CloudFlare.Client.Api.Result;
using CloudFlare.Client.Api.Zones;
using CloudFlare.Client.Api.Zones.DnsRecord;
using CloudFlare.Client.Enumerators;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories.Domains;
using Moonlight.App.Services.LogServices;
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
using MatchType = CloudFlare.Client.Enumerators.MatchType;
namespace Moonlight.App.Services;
@@ -21,18 +22,15 @@ public class DomainService
private readonly DomainRepository DomainRepository;
private readonly SharedDomainRepository SharedDomainRepository;
private readonly CloudFlareClient Client;
private readonly AuditLogService AuditLogService;
private readonly string AccountId;
public DomainService(
ConfigService configService,
DomainRepository domainRepository,
SharedDomainRepository sharedDomainRepository,
AuditLogService auditLogService)
SharedDomainRepository sharedDomainRepository)
{
DomainRepository = domainRepository;
SharedDomainRepository = sharedDomainRepository;
AuditLogService = auditLogService;
var config = configService
.GetSection("Moonlight")
@@ -52,7 +50,7 @@ public class DomainService
{
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
throw new DisplayException("A domain with this name does already exist for this shared domain");
var res = DomainRepository.Add(new()
{
Name = domain,
@@ -66,7 +64,7 @@ public class DomainService
public Task Delete(Domain domain)
{
DomainRepository.Delete(domain);
return Task.CompletedTask;
}
@@ -97,9 +95,36 @@ public class DomainService
{
var domain = EnsureData(d);
var records = GetData(
await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId)
);
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
// Load paginated
// TODO: Find an alternative option. This way to load the records is NOT optimal
// and can result in long loading time when there are many dns records.
// As cloudflare does not offer a way to search dns records which starts
// with a specific string we are not able to filter it using the api (client)
var initialResponse = await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId);
records.AddRange(GetData(initialResponse));
// Check if there are more pages
while (initialResponse.ResultInfo.Page < initialResponse.ResultInfo.TotalPage)
{
// Get the next page of data
var nextPageResponse = await Client.Zones.DnsRecords.GetAsync(
domain.SharedDomain.CloudflareId,
displayOptions: new()
{
Page = initialResponse.ResultInfo.Page + 1
}
);
var nextPageRecords = GetData(nextPageResponse);
// Append the records from the next page to the existing records
records.AddRange(nextPageRecords);
// Update the initial response to the next page response
initialResponse = nextPageResponse;
}
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}";
@@ -149,7 +174,11 @@ public class DomainService
if (dnsRecord.Type == DnsRecordType.Srv)
{
var parts = dnsRecord.Name.Split(".");
Enum.TryParse(parts[1], out Protocol protocol);
Protocol protocol = Protocol.Tcp;
if (parts[1].Contains("udp"))
protocol = Protocol.Udp;
var valueParts = dnsRecord.Content.Split(" ");
@@ -191,11 +220,7 @@ public class DomainService
}));
}
await AuditLogService.Log(AuditLogType.AddDomainRecord, x =>
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
//TODO: AuditLog
}
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
@@ -224,12 +249,8 @@ public class DomainService
Type = dnsRecord.Type
}));
}
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, x =>
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
//TODO: AuditLog
}
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
@@ -239,12 +260,8 @@ public class DomainService
GetData(
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
);
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, x =>
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
//TODO: AuditLog
}
private Domain EnsureData(Domain domain)

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Moonlight.App.Helpers;
using Moonlight.App.Helpers;
namespace Moonlight.App.Services.Files;

View File

@@ -1,93 +0,0 @@
using Moonlight.App.Database.Entities.LogsEntries;
using Moonlight.App.Models.Log;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories.LogEntries;
using Moonlight.App.Services.Sessions;
using Newtonsoft.Json;
namespace Moonlight.App.Services.LogServices;
public class AuditLogService
{
private readonly AuditLogEntryRepository Repository;
private readonly IHttpContextAccessor HttpContextAccessor;
public AuditLogService(
AuditLogEntryRepository repository,
IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(AuditLogType type, Action<AuditLogParameters> data)
{
var ip = GetIp();
var al = new AuditLogParameters();
data(al);
var entry = new AuditLogEntry()
{
Ip = ip,
Type = type,
System = false,
JsonData = al.Build()
};
Repository.Add(entry);
return Task.CompletedTask;
}
public Task LogSystem(AuditLogType type, Action<AuditLogParameters> data)
{
var al = new AuditLogParameters();
data(al);
var entry = new AuditLogEntry()
{
Type = type,
System = true,
JsonData = al.Build()
};
Repository.Add(entry);
return Task.CompletedTask;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
public class AuditLogParameters
{
private List<LogData> Data = new List<LogData>();
public void Add<T>(object? data)
{
if(data == null)
return;
Data.Add(new LogData()
{
Type = typeof(T),
Value = data.ToString()
});
}
internal string Build()
{
return JsonConvert.SerializeObject(Data);
}
}
}

View File

@@ -1,118 +0,0 @@
using System.Diagnostics;
using System.Reflection;
using Moonlight.App.Database.Entities.LogsEntries;
using Moonlight.App.Models.Log;
using Moonlight.App.Repositories.LogEntries;
using Moonlight.App.Services.Sessions;
using Newtonsoft.Json;
namespace Moonlight.App.Services.LogServices;
public class ErrorLogService
{
private readonly ErrorLogEntryRepository Repository;
private readonly IHttpContextAccessor HttpContextAccessor;
public ErrorLogService(ErrorLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(Exception exception, Action<ErrorLogParameters> data)
{
var ip = GetIp();
var al = new ErrorLogParameters();
data(al);
var entry = new ErrorLogEntry()
{
Ip = ip,
System = false,
JsonData = al.Build(),
Class = NameOfCallingClass(),
Stacktrace = exception.ToStringDemystified()
};
Repository.Add(entry);
return Task.CompletedTask;
}
public Task LogSystem(Exception exception, Action<ErrorLogParameters> data)
{
var al = new ErrorLogParameters();
data(al);
var entry = new ErrorLogEntry()
{
System = true,
JsonData = al.Build(),
Class = NameOfCallingClass(),
Stacktrace = exception.ToStringDemystified()
};
Repository.Add(entry);
return Task.CompletedTask;
}
private string NameOfCallingClass(int skipFrames = 4)
{
string fullName;
Type? declaringType;
do
{
MethodBase method = new StackFrame(skipFrames, false).GetMethod()!;
declaringType = method.DeclaringType;
if (declaringType == null)
{
return method.Name;
}
skipFrames++;
if (declaringType.Name.Contains("<"))
fullName = declaringType.ReflectedType!.Name;
else
fullName = declaringType.Name;
}
while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase) | fullName.Contains("Log"));
return fullName;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
public class ErrorLogParameters
{
private List<LogData> Data = new List<LogData>();
public void Add<T>(object? data)
{
if(data == null)
return;
Data.Add(new LogData()
{
Type = typeof(T),
Value = data.ToString()
});
}
internal string Build()
{
return JsonConvert.SerializeObject(Data);
}
}
}

View File

@@ -1,45 +0,0 @@
using Logging.Net;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Services.LogServices;
public class LogService
{
public LogService()
{
Task.Run(ClearLog);
}
private async Task ClearLog()
{
while (true)
{
await Task.Delay(TimeSpan.FromMinutes(15));
if (GetMessages().Length > 500)
{
if (Logger.UsedLogger is CacheLogger cacheLogger)
{
cacheLogger.Clear(250); //TODO: config
}
else
{
Logger.Warn("Log service cannot access cache. Is Logging.Net using CacheLogger?");
}
}
}
}
public LogEntry[] GetMessages()
{
if (Logger.UsedLogger is CacheLogger cacheLogger)
{
return cacheLogger.GetMessages();
}
Logger.Warn("Log service cannot access cache. Is Logging.Net using CacheLogger?");
return Array.Empty<LogEntry>();
}
}

View File

@@ -1,92 +0,0 @@
using Moonlight.App.Database.Entities.LogsEntries;
using Moonlight.App.Models.Log;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories.LogEntries;
using Moonlight.App.Services.Sessions;
using Newtonsoft.Json;
namespace Moonlight.App.Services.LogServices;
public class SecurityLogService
{
private readonly SecurityLogEntryRepository Repository;
private readonly IHttpContextAccessor HttpContextAccessor;
public SecurityLogService(SecurityLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(SecurityLogType type, Action<SecurityLogParameters> data)
{
var ip = GetIp();
var al = new SecurityLogParameters();
data(al);
var entry = new SecurityLogEntry()
{
Ip = ip,
Type = type,
System = false,
JsonData = al.Build()
};
Repository.Add(entry);
return Task.CompletedTask;
}
public Task LogSystem(SecurityLogType type, Action<SecurityLogParameters> data)
{
var al = new SecurityLogParameters();
data(al);
var entry = new SecurityLogEntry()
{
Type = type,
System = true,
JsonData = al.Build()
};
Repository.Add(entry);
return Task.CompletedTask;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
public class SecurityLogParameters
{
private List<LogData> Data = new List<LogData>();
public void Add<T>(object? data)
{
if(data == null)
return;
Data.Add(new LogData()
{
Type = typeof(T),
Value = data.ToString()
});
}
internal string Build()
{
return JsonConvert.SerializeObject(Data);
}
}
}

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using MimeKit;
using MimeKit;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;

View File

@@ -1,5 +1,5 @@
using System.Net;
using Logging.Net;
using Moonlight.App.Helpers;
namespace Moonlight.App.Services.Mail;

View File

@@ -1,4 +1,4 @@
using Logging.Net;
using Moonlight.App.Helpers;
using Octokit;
using Repository = LibGit2Sharp.Repository;

View File

@@ -4,9 +4,7 @@ using JWT.Builder;
using JWT.Exceptions;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services;
@@ -14,15 +12,12 @@ public class OneTimeJwtService
{
private readonly ConfigService ConfigService;
private readonly RevokeRepository RevokeRepository;
private readonly SecurityLogService SecurityLogService;
public OneTimeJwtService(ConfigService configService,
RevokeRepository revokeRepository,
SecurityLogService securityLogService)
RevokeRepository revokeRepository)
{
ConfigService = configService;
RevokeRepository = revokeRepository;
SecurityLogService = securityLogService;
}
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
@@ -76,10 +71,7 @@ public class OneTimeJwtService
}
catch (SignatureVerificationException)
{
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x =>
{
x.Add<string>(token);
});
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
return null;
}
catch (Exception e)

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.ApiClients.Wings.Requests;
using Moonlight.App.ApiClients.Wings.Resources;
@@ -13,7 +12,6 @@ using Moonlight.App.Helpers.Wings;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
using Moonlight.App.Services.LogServices;
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
namespace Moonlight.App.Services;
@@ -30,9 +28,6 @@ public class ServerService
private readonly UserService UserService;
private readonly ConfigService ConfigService;
private readonly WingsJwtHelper WingsJwtHelper;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService;
private readonly NodeService NodeService;
private readonly DateTimeService DateTimeService;
private readonly EventSystem Event;
@@ -46,9 +41,6 @@ public class ServerService
UserService userService,
ConfigService configService,
WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ErrorLogService errorLogService,
NodeService nodeService,
NodeAllocationRepository nodeAllocationRepository,
DateTimeService dateTimeService,
@@ -63,9 +55,6 @@ public class ServerService
UserService = userService;
ConfigService = configService;
WingsJwtHelper = wingsJwtHelper;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
ErrorLogService = errorLogService;
NodeService = nodeService;
NodeAllocationRepository = nodeAllocationRepository;
DateTimeService = dateTimeService;
@@ -107,11 +96,7 @@ public class ServerService
Action = rawSignal
});
await AuditLogService.Log(AuditLogType.ChangePowerState, x =>
{
x.Add<Server>(server.Uuid);
x.Add<PowerSignal>(rawSignal);
});
//TODO: AuditLog
}
public async Task<ServerBackup> CreateBackup(Server server)
@@ -140,12 +125,7 @@ public class ServerService
Ignore = ""
});
await AuditLogService.Log(AuditLogType.CreateBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
});
//TODO: AuditLog
return backup;
}
@@ -182,12 +162,7 @@ public class ServerService
Adapter = "wings"
});
await AuditLogService.Log(AuditLogType.RestoreBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
//TODO: AuditLog
}
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
@@ -220,13 +195,7 @@ public class ServerService
await Event.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
}
);
//TODO: AuditLog
}
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
@@ -239,12 +208,7 @@ public class ServerService
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
});
await AuditLogService.Log(AuditLogType.DownloadBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
//TODO: AuditLog
if (server.Node.Ssl)
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
@@ -346,17 +310,14 @@ public class ServerService
StartOnCompletion = false
});
await AuditLogService.Log(AuditLogType.CreateServer, x => { x.Add<Server>(newServerData.Uuid); });
//TODO: AuditLog
return newServerData;
}
catch (Exception e)
{
await ErrorLogService.Log(e, x =>
{
x.Add<Server>(newServerData.Uuid);
x.Add<Node>(node.Id);
});
Logger.Error("Error creating server on wings");
Logger.Error(e);
ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff
@@ -373,7 +334,7 @@ public class ServerService
server.Installing = true;
ServerRepository.Update(server);
await AuditLogService.Log(AuditLogType.ReinstallServer, x => { x.Add<Server>(server.Uuid); });
//TODO: AuditLog
}
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
@@ -382,7 +343,7 @@ public class ServerService
if (server == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x => { x.Add<int>(id); });
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Server not found");
}

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Moonlight.App.Services.Files;
using Moonlight.App.Services.Files;
namespace Moonlight.App.Services.Sessions;

View File

@@ -2,12 +2,10 @@
using JWT.Algorithms;
using JWT.Builder;
using JWT.Exceptions;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using UAParser;
namespace Moonlight.App.Services.Sessions;
@@ -16,8 +14,6 @@ public class IdentityService
{
private readonly UserRepository UserRepository;
private readonly CookieService CookieService;
private readonly SecurityLogService SecurityLogService;
private readonly ErrorLogService ErrorLogService;
private readonly IHttpContextAccessor HttpContextAccessor;
private readonly string Secret;
@@ -27,15 +23,11 @@ public class IdentityService
CookieService cookieService,
UserRepository userRepository,
IHttpContextAccessor httpContextAccessor,
ConfigService configService,
SecurityLogService securityLogService,
ErrorLogService errorLogService)
ConfigService configService)
{
CookieService = cookieService;
UserRepository = userRepository;
HttpContextAccessor = httpContextAccessor;
SecurityLogService = securityLogService;
ErrorLogService = errorLogService;
Secret = configService
.GetSection("Moonlight")
@@ -90,15 +82,13 @@ public class IdentityService
}
catch (SignatureVerificationException)
{
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, x =>
{
x.Add<string>(token);
});
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
return null;
}
catch (Exception e)
{
await ErrorLogService.Log(e, x => {});
Logger.Error("Error reading jwt");
Logger.Error(e);
return null;
}
@@ -134,7 +124,8 @@ public class IdentityService
}
catch (Exception e)
{
await ErrorLogService.Log(e, x => {});
Logger.Error("Unexpected error while processing token");
Logger.Error(e);
return null;
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.JSInterop;
namespace Moonlight.App.Services.Sessions;
public class KeyListenerService
{
private readonly IJSRuntime _jsRuntime;
private DotNetObjectReference<KeyListenerService> _objRef;
public event EventHandler<string> KeyPressed;
public KeyListenerService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task Initialize()
{
_objRef = DotNetObjectReference.Create(this);
await _jsRuntime.InvokeVoidAsync("moonlight.keyListener.register", _objRef);
}
[JSInvokable]
public void OnKeyPress(string key)
{
KeyPressed?.Invoke(this, key);
}
public async ValueTask DisposeAsync()
{
try
{
await _jsRuntime.InvokeVoidAsync("moonlight.keyListener.unregister", _objRef);
_objRef.Dispose();
}
catch (Exception) { /* ignored */}
}
}

View File

@@ -78,7 +78,7 @@ public class SmartTranslateService
}
catch (Exception ex)
{
Logging.Net.Logger.Error(ex);
Logger.Error(ex);
return key;
}
}

View File

@@ -1,6 +1,5 @@
using Logging.Net;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Sessions;
@@ -66,8 +65,6 @@ public class StatisticsCaptureService
AddEntry("databasesCount", databasesRepo.Get().Count());
AddEntry("sessionsCount", sessionService.GetAll().Length);
}
Logger.Log("Statistics are weird");
}
catch (Exception e)
{

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Forms;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Services.Files;

View File

@@ -1,7 +1,5 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Misc;
using Moonlight.App.Exceptions;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions;
using OtpNet;
@@ -11,16 +9,13 @@ public class TotpService
{
private readonly IdentityService IdentityService;
private readonly UserRepository UserRepository;
private readonly AuditLogService AuditLogService;
public TotpService(
IdentityService identityService,
UserRepository userRepository,
AuditLogService auditLogService)
UserRepository userRepository)
{
IdentityService = identityService;
UserRepository = userRepository;
AuditLogService = auditLogService;
}
public Task<bool> Verify(string secret, string code)
@@ -44,24 +39,24 @@ public class TotpService
return user!.TotpSecret;
}
public async Task Enable()
public async Task GenerateSecret()
{
var user = (await IdentityService.Get())!;
user.TotpSecret = GenerateSecret();
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.EnableTotp, x =>
{
x.Add<User>(user.Email);
});
}
public async Task EnforceTotpLogin()
public async Task Enable(string code)
{
var user = (await IdentityService.Get())!;
if (!await Verify(user.TotpSecret, code))
{
throw new DisplayException("The 2fa code you entered is invalid");
}
user.TotpEnabled = true;
UserRepository.Update(user);
}
@@ -71,17 +66,10 @@ public class TotpService
var user = (await IdentityService.Get())!;
user.TotpEnabled = false;
user.TotpSecret = "";
UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.DisableTotp,x =>
{
x.Add<User>(user.Email);
});
}
private string GenerateSecret()
{
return Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));
//TODO: AuditLog
}
}

View File

@@ -5,7 +5,6 @@ using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Mail;
using Moonlight.App.Services.Sessions;
@@ -15,8 +14,6 @@ public class UserService
{
private readonly UserRepository UserRepository;
private readonly TotpService TotpService;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly MailService MailService;
private readonly IdentityService IdentityService;
private readonly IpLocateService IpLocateService;
@@ -27,9 +24,7 @@ public class UserService
public UserService(
UserRepository userRepository,
TotpService totpService,
ConfigService configService,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ConfigService configService,
MailService mailService,
IdentityService identityService,
IpLocateService ipLocateService,
@@ -37,8 +32,6 @@ public class UserService
{
UserRepository = userRepository;
TotpService = totpService;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
MailService = mailService;
IdentityService = identityService;
IpLocateService = ipLocateService;
@@ -85,10 +78,7 @@ public class UserService
await MailService.SendMail(user!, "register", values => {});
await AuditLogService.Log(AuditLogType.Register, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
return await GenerateToken(user);
}
@@ -102,11 +92,7 @@ public class UserService
if (user == null)
{
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("Email and password combination not found");
}
@@ -115,11 +101,7 @@ public class UserService
return user.TotpEnabled;
}
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("Email and password combination not found");;
}
@@ -144,28 +126,18 @@ public class UserService
if (totpCodeValid)
{
await AuditLogService.Log(AuditLogType.Login, x =>
{
x.Add<User>(email);
});
//TODO: AuditLog
return await GenerateToken(user, true);
}
else
{
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("2FA code invalid");
}
}
else
{
await AuditLogService.Log(AuditLogType.Login, x =>
{
x.Add<User>(email);
});
//TODO: AuditLog
return await GenerateToken(user!, true);
}
}
@@ -178,10 +150,7 @@ public class UserService
if (isSystemAction)
{
await AuditLogService.LogSystem(AuditLogType.ChangePassword, x=>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
}
else
{
@@ -194,10 +163,7 @@ public class UserService
values.Add("Location", location);
});
await AuditLogService.Log(AuditLogType.ChangePassword, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
}
}
@@ -207,28 +173,18 @@ public class UserService
if (user == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
{
x.Add<int>(id);
});
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Invalid username");
}
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{
await AuditLogService.LogSystem(AuditLogType.Login, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
return user;
}
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
{
x.Add<int>(id);
x.Add<string>(password);
});
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Invalid userid or password");
}
@@ -271,7 +227,7 @@ public class UserService
var newPassword = StringHelper.GenerateString(16);
await ChangePassword(user, newPassword, true);
await AuditLogService.Log(AuditLogType.PasswordReset, x => {});
//TODO: AuditLog
var location = await IpLocateService.GetLocation();

View File

@@ -1,6 +1,4 @@
using System.Net;
using DnsClient;
using Logging.Net;
using DnsClient;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.CloudPanel.Requests;

View File

@@ -26,7 +26,6 @@
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
<PackageReference Include="JWT" Version="10.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.27.2" />
<PackageReference Include="Logging.Net" Version="1.1.3" />
<PackageReference Include="MailKit" Version="4.0.0" />
<PackageReference Include="Mappy.Net" Version="1.0.2" />
<PackageReference Include="Markdig" Version="0.31.0" />
@@ -47,6 +46,9 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
<PackageReference Include="Serilog" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
<PackageReference Include="SSH.NET" Version="2020.0.2" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="XtermBlazor" Version="1.8.1" />

View File

@@ -103,6 +103,8 @@
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@@shopify/draggable@1.0.0-beta.11/lib/draggable.bundle.js"></script>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>

View File

@@ -2,7 +2,6 @@ using BlazorDownloadFile;
using BlazorTable;
using CurrieTechnologies.Razor.SweetAlert2;
using HealthChecks.UI.Client;
using Logging.Net;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Modrinth;
@@ -24,13 +23,14 @@ using Moonlight.App.Services.Background;
using Moonlight.App.Services.DiscordBot;
using Moonlight.App.Services.Files;
using Moonlight.App.Services.Interop;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Mail;
using Moonlight.App.Services.Minecraft;
using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.Sessions;
using Moonlight.App.Services.Statistics;
using Moonlight.App.Services.SupportChat;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;
namespace Moonlight
{
@@ -38,14 +38,31 @@ namespace Moonlight
{
public static async Task Main(string[] args)
{
Logger.UsedLogger = new CacheLogger();
// This will also copy all default config files
var configService = new ConfigService(new StorageService());
if (configService.DebugMode)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}
else
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
Logger.Info("Running pre-init tasks");
// This will also copy all default config files
var configService = new ConfigService(new StorageService());
var databaseCheckupService = new DatabaseCheckupService(configService);
await databaseCheckupService.Perform();
@@ -134,15 +151,12 @@ namespace Moonlight
builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddScoped<DynamicBackgroundService>();
builder.Services.AddScoped<ServerAddonPluginService>();
builder.Services.AddScoped<KeyListenerService>();
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();
// Loggers
builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<ErrorLogService>();
builder.Services.AddScoped<LogService>();
builder.Services.AddScoped<MailService>();
builder.Services.AddSingleton<TrashMailDetectorService>();
@@ -168,6 +182,7 @@ namespace Moonlight
builder.Services.AddSingleton<StatisticsCaptureService>();
builder.Services.AddSingleton<DiscordNotificationService>();
builder.Services.AddSingleton<CleanupService>();
builder.Services.AddSingleton<MalwareScanService>();
// Other
builder.Services.AddSingleton<MoonlightService>();
@@ -206,6 +221,7 @@ namespace Moonlight
_ = app.Services.GetRequiredService<DiscordBotService>();
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
_ = app.Services.GetRequiredService<DiscordNotificationService>();
_ = app.Services.GetRequiredService<MalwareScanService>();
_ = app.Services.GetRequiredService<MoonlightService>();

View File

@@ -31,6 +31,20 @@
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true
},
"Watch": {
"commandName": "Executable",
"executablePath": "dotnet",
"workingDirectory": "$(ProjectDir)",
"hotReloadEnabled": true,
"hotReloadProfile": "aspnetcore",
"commandLineArgs": "watch run",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ML_DEBUG": "true"
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118"
}
}
}

View File

@@ -0,0 +1,31 @@
<div class="mx-auto">
<div class="card">
<div class="d-flex justify-content-center pt-5">
<img height="300" width="300" src="/assets/media/svg/notfound.svg" alt="Not found"/>
</div>
<span class="card-title text-center fs-3">
<TL>The requested resource was not found</TL>
</span>
<p class="card-body text-center fs-4 text-gray-800">
<TL>We were not able to find the requested resource. This can have following reasons</TL>
<div class="mt-4 d-flex flex-column align-items-center">
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>The resource was deleted</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You have to permission to access this resource</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You may have entered invalid data</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>A unknown bug occured</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>An api was down and not proper handled</TL>
</li>
</div>
</p>
</div>
</div>

View File

@@ -8,11 +8,11 @@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services
@using Moonlight.App.Exceptions
@using Logging.Net
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Sessions
@using System.ComponentModel.DataAnnotations
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Forms
@inject AlertService AlertService
@@ -96,7 +96,7 @@
{
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
<div class="fv-row mb-8 fv-plugins-icon-container">
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent"></InputText>
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent" placeholder="@(SmartTranslateService.Translate("2fa code"))"></InputText>
</div>
<div class="d-grid mb-10">
<button type="submit" class="btn btn-primary">

View File

@@ -1,66 +0,0 @@
@using Logging.Net
@using Moonlight.App.Services.LogServices
@using Moonlight.App.Services.Sessions
@inherits ErrorBoundary
@inject ErrorLogService ErrorLogService
@if (CurrentException is null)
{
@ChildContent
}
else if (ErrorContent is not null)
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. This component is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This component is crashed. The error has been reported to the moonlight team</TL>
</div>
</div>
</div>
</div>
}
else
{
<div class="card card-flush h-md-100">
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
<div class="mb-10">
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
<span class="me-2">
<TL>Ooops. This component is crashed</TL>
</span>
</div>
<div class="text-center">
<TL>This component is crashed. The error has been reported to the moonlight team</TL>
</div>
</div>
</div>
</div>
}
@code
{
List<Exception> receivedExceptions = new();
protected override async Task OnErrorAsync(Exception exception)
{
receivedExceptions.Add(exception);
await ErrorLogService.Log(exception, x => {});
await base.OnErrorAsync(exception);
}
public new void Recover()
{
receivedExceptions.Clear();
base.Recover();
}
}

View File

@@ -1,5 +1,5 @@
@using Logging.Net
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Helpers
@inherits ErrorBoundary

View File

@@ -1,5 +1,5 @@
@using Logging.Net
@using Moonlight.App.Exceptions
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions

View File

@@ -1,11 +1,11 @@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Exceptions
@using Moonlight.App.Services
@using Logging.Net
@using Moonlight.App.ApiClients.CloudPanel
@using Moonlight.App.ApiClients.Daemon
@using Moonlight.App.ApiClients.Modrinth
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Helpers
@inherits ErrorBoundaryBase
@inject AlertService AlertService
@@ -42,7 +42,7 @@ else
{
if (ConfigService.DebugMode)
{
Logger.Warn(exception);
Logger.Verbose(exception);
}
if (exception is DisplayException displayException)

View File

@@ -0,0 +1,78 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services.Interop
@inject ModalService ModalService
<div class="modal fade" id="connectionDetails" tabindex="-1" aria-labelledby="connectionDetails" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<TL>Connection details</TL>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Port)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Username)">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<TL>Close</TL>
</button>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public FileAccess Access { get; set; }
private string Host = "";
private string Username = "";
private int Port;
protected override async Task OnParametersSetAsync()
{
Uri uri = new Uri(await Access.GetLaunchUrl());
Host = uri.Host;
Port = uri.Port;
Username = uri.UserInfo.Split(':')[0];
}
public async Task Show()
{
await ModalService.Show("connectionDetails");
}
}

View File

@@ -1,10 +1,15 @@
@using BlazorMonaco
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Partials
@inject SmartTranslateService TranslationService
@inject KeyListenerService KeyListenerService
@inject IJSRuntime JsRuntime
@implements IDisposable
<div class="card bg-black rounded">
<div class="card-body">
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
@@ -65,6 +70,16 @@
},
AutoIndent = true
};
KeyListenerService.KeyPressed += KeyPressed;
}
private async void KeyPressed(object? sender, string e)
{
if (e == "saveShortcut")
{
await Submit();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -111,4 +126,10 @@
{
return await Editor.GetValue();
}
public void Dispose()
{
Editor.Dispose();
KeyListenerService.KeyPressed -= KeyPressed;
}
}

View File

@@ -1,6 +1,5 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using Logging.Net
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorDownloadFile
@@ -17,7 +16,7 @@
InitialData="@EditorInitialData"
Language="@EditorLanguage"
OnCancel="() => Cancel()"
OnSubmit="(_) => Cancel(true)"
OnSubmit="(_) => Save()"
HideControls="false">
</FileEditor>
}
@@ -27,7 +26,7 @@ else
<div class="card-header border-0 my-2">
<div class="card-title">
<div class="d-flex flex-stack">
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
</div>
</div>
<div class="card-toolbar">
@@ -35,21 +34,23 @@ else
@if (View != null && View.SelectedFiles.Any())
{
<div class="fw-bold me-5">
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
<span class="me-2">
@(View.SelectedFiles.Length) <TL>selected</TL>
</span>
</div>
<WButton Text="@(SmartTranslateService.Translate("Move"))"
WorkingText="@(SmartTranslateService.Translate("Moving"))"
CssClasses="btn-primary me-3"
OnClick="StartMoveFiles">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
CssClasses="btn-primary me-3"
OnClick="CompressMultiple">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
@@ -58,37 +59,46 @@ else
}
else
{
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-muted svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
</svg>
</span>
<TL>Launch WinSCP</TL>
</button>
<div class="btn-group me-3">
<button type="button" @onclick="Launch" class="btn btn-light-primary">
<TL>Launch WinSCP</TL>
</button>
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
<TL>Download WinSCP</TL>
</a>
</li>
<li>
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
<TL>Show connection details</TL>
</button>
</li>
</ul>
</div>
<button type="button" @onclick="CreateFile" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M6 22h12a2 2 0 0 0 2-2V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2zm7-18 5 5h-5V4zM8 14h3v-3h2v3h3v2h-3v3h-2v-3H8v-2z"></path>
</svg>
</span>
<TL>New file</TL>
</button>
<div class="btn-group me-3">
<button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<TL>New</TL>&nbsp;
</button>
<ul class="dropdown-menu">
<li>
<button @onclick="CreateFile" class="dropdown-item btn">
<TL>New file</TL>
</button>
</li>
<li>
<button @onclick="CreateFolder" class="dropdown-item btn">
<TL>New folder</TL>
</button>
</li>
</ul>
</div>
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
<span class="svg-icon svg-icon-2">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
</svg>
</span>
<TL>New folder</TL>
</button>
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
}
</div>
</div>
@@ -104,13 +114,15 @@ else
DisableScrolling="true">
</FileView>
</div>
<FileSelectModal @ref="FileSelectModal"
OnlyFolder="true"
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
Access="MoveAccess"
OnSubmit="OnFileMoveSubmit">
</FileSelectModal>
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
}
@code
@@ -132,16 +144,19 @@ else
private FileAccess MoveAccess;
private FileSelectModal FileSelectModal;
private FileData? SingleMoveFile = null;
// Config
private ContextAction[] Actions = Array.Empty<ContextAction>();
// Connection details
private ConnectionDetailsModal ConnectionDetailsModal;
protected override void OnInitialized()
{
MoveAccess = (FileAccess)Access.Clone();
List<ContextAction> actions = new();
actions.Add(new()
{
Id = "rename",
@@ -152,18 +167,18 @@ else
SmartTranslateService.Translate("Rename"),
SmartTranslateService.Translate("Enter a new name"),
x.Name
);
);
if (name != x.Name)
{
await Access.Move(x, Access.CurrentPath + name);
}
await View!.Refresh();
}
});
actions.Add(new ()
actions.Add(new()
{
Id = "download",
Name = "Download",
@@ -189,7 +204,7 @@ else
await ToastService.Error(SmartTranslateService.Translate("You are not able to download folders using the moonlight file manager"));
}
});
actions.Add(new()
{
Id = "compress",
@@ -200,8 +215,8 @@ else
await View!.Refresh();
}
});
actions.Add(new ()
actions.Add(new()
{
Id = "decompress",
Name = "Decompress",
@@ -222,7 +237,7 @@ else
await StartMoveFiles();
}
});
actions.Add(new()
{
Id = "delete",
@@ -233,7 +248,7 @@ else
await View!.Refresh();
}
});
Actions = actions.ToArray();
}
@@ -250,11 +265,18 @@ else
await InvokeAsync(StateHasChanged);
return true;
}
await InvokeAsync(StateHasChanged);
return false;
}
private async void Save()
{
var data = await Editor.GetData();
await Access.Write(EditingFile, data);
await ToastService.Success(SmartTranslateService.Translate("Successfully saved file"));
}
private async void Cancel(bool save = false)
{
if (save)
@@ -299,7 +321,7 @@ else
if (string.IsNullOrEmpty(name))
return;
await Access.Write(new FileData{IsFile = true, Name = name}, "");
await Access.Write(new FileData { IsFile = true, Name = name }, "");
await View!.Refresh();
}
@@ -326,7 +348,7 @@ else
private async Task CompressMultiple()
{
await Access.Compress(View!.SelectedFiles);
await View!.Refresh();
}
@@ -336,7 +358,7 @@ else
{
await Access.Move(sFile, path + sFile.Name);
}
if (SingleMoveFile != null)
{
await Access.Move(SingleMoveFile, path + SingleMoveFile.Name);

View File

@@ -1,5 +1,4 @@
@using Moonlight.App.Helpers.Files
@using Logging.Net
<div class="badge badge-lg badge-light-primary">
<div class="d-flex align-items-center flex-wrap">

View File

@@ -1,7 +1,7 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Logging.Net
@using Moonlight.App.Helpers
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService

View File

@@ -1,5 +1,4 @@
@using Moonlight.App.Helpers.Files
@using Logging.Net
@using BlazorContextMenu
@using Moonlight.App.Helpers

View File

@@ -1,5 +1,4 @@
@typeparam T
@using Logging.Net
@inherits InputBase<T>
<div class="dropdown w-100">

View File

@@ -1,7 +1,6 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Logging.Net
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService

View File

@@ -1,5 +1,4 @@
@using Moonlight.App.Services.Interop
@using Logging.Net
@inject ReCaptchaService ReCaptchaService

View File

@@ -2,6 +2,7 @@
{
<button class="btn @(CssClasses)" @onclick="Do">
@Text
@ChildContent
</button>
}
else
@@ -20,14 +21,17 @@ else
public string CssClasses { get; set; } = "btn-primary";
[Parameter]
public string Text { get; set; } = "Mache was";
public string Text { get; set; } = "";
[Parameter]
public string WorkingText { get; set; } = "Verarbeite...";
public string WorkingText { get; set; } = "";
[Parameter]
public Func<Task>? OnClick { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private async Task Do()
{
Working = true;

View File

@@ -15,8 +15,8 @@
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/auditlog">
<TL>AuditLog</TL>
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/malware">
<TL>Malware</TL>
</a>
</li>
<li class="nav-item mt-2">

View File

@@ -8,7 +8,7 @@
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-2">
<a class="text-gray-900 fs-2 fw-bold me-1">@(User.FirstName) @(User.LastName)</a>
<a class="text-gray-900 fs-2 fw-bold me-1 @(User.StreamerMode ? "blur" : "")">@(User.FirstName) @(User.LastName)</a>
@if (User.Status == UserStatus.Verified)
{
@@ -16,7 +16,7 @@
}
</div>
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
<span class="d-flex align-items-center text-gray-400 mb-2">
<span class="d-flex align-items-center text-gray-400 mb-2 @(User.StreamerMode ? "blur" : "")">
@(User.Email)
</span>
</div>

View File

@@ -69,14 +69,16 @@
</div>
<div class="d-flex flex-column">
<div class="fw-bold d-flex align-items-center fs-5">
@(User.FirstName) @(User.LastName)
<div class="@(User.StreamerMode ? "blur" : "")">
@(User.FirstName) @(User.LastName)
</div>
@if (User.Admin)
{
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
}
</div>
<a class="fw-semibold text-muted text-hover-primary fs-7">@(User.Email)</a>
<a class="fw-semibold text-muted text-hover-primary fs-7 @(User.StreamerMode ? "blur" : "")">@(User.Email)</a>
</div>
</div>
</div>

View File

@@ -1,5 +1,4 @@
@using Microsoft.AspNetCore.Components.Rendering
@using Logging.Net
@using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService

View File

@@ -21,6 +21,7 @@
@inject SmartTranslateService SmartTranslateService
@inject IpBanService IpBanService
@inject DynamicBackgroundService DynamicBackgroundService
@inject KeyListenerService KeyListenerService
@{
var uri = new Uri(NavigationManager.Uri);
@@ -207,10 +208,14 @@
UserProcessed = true;
await InvokeAsync(StateHasChanged);
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
try
{
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
}
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
await SessionService.Register();
@@ -236,6 +241,8 @@
});
}
await KeyListenerService.Initialize();
RunDelayedMenu(0);
RunDelayedMenu(1);
RunDelayedMenu(3);
@@ -252,6 +259,8 @@
{
SessionService.Close();
await KeyListenerService.DisposeAsync();
if (User != null)
{
await Event.Off($"supportChat.{User.Id}.message", this);

View File

@@ -3,10 +3,10 @@
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Newtonsoft.Json
@using Logging.Net
@inject ServerRepository ServerRepository
@inject UserRepository UserRepository

View File

@@ -4,9 +4,9 @@
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Logging.Net
@using BlazorTable
@using Moonlight.App.ApiClients.Wings.Resources
@using Moonlight.App.Helpers
@inject NodeRepository NodeRepository
@inject AlertService AlertService
@@ -131,7 +131,8 @@
}
catch (Exception e)
{
Logger.Debug(e.Message);
Logger.Verbose($"Error fetching status for node '{node.Name}'");
Logger.Verbose(e);
}
await InvokeAsync(StateHasChanged);

View File

@@ -1,13 +1,8 @@
@page "/admin/servers/cleanup"
@using Moonlight.App.Services
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.LogServices
@using Moonlight.App.Events
@using Moonlight.App.Services.Background
@inject CleanupService CleanupService
@inject AuditLogService AuditLogService
@inject EventSystem Event
@implements IDisposable

View File

@@ -1,13 +1,13 @@
@page "/admin/servers/images"
@using BlazorTable
@using Logging.Net
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using System.Text
@using Moonlight.App.Helpers
@using Newtonsoft.Json
@inject Repository<Image> ImageRepository

View File

@@ -4,8 +4,8 @@
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Interop
@using Logging.Net
@using Moonlight.App.Models.Forms
@inject NodeRepository NodeRepository

View File

@@ -0,0 +1,85 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using BlazorTable
@inject Repository<Server> ServerRepository
@inject Repository<NodeAllocation> NodeAllocationRepository
@inject AlertService AlertService
@inject SmartTranslateService SmartTranslateService
<div class="row">
<div class="col-12 col-md-6 mb-5">
<div class="card card-body">
<WButton Text="@(SmartTranslateService.Translate("Add allocation"))"
WorkingText="@(SmartTranslateService.Translate("Searching"))"
CssClasses="btn-primary"
OnClick="AddAllocation">
</WButton>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card card-body">
<div class="table-responsive">
<table class="table table-bordered">
<tbody>
@foreach (var allocation in Server.Allocations)
{
<tr>
<td class="align-middle fs-5">
@(Server.Node.Fqdn + ":" + allocation.Port)
</td>
<td>
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
CssClasses="btn-danger"
OnClick="() => DeleteAllocation(allocation)">
</WButton>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public Server Server { get; set; }
private async Task AddAllocation()
{
// We have sadly no choice to use entity framework to do what the sql call does, there
// are only slower ways, so we will use a raw sql call as a exception
var freeAllocation = NodeAllocationRepository
.Get()
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={Server.Node.Id} LIMIT 1")
.FirstOrDefault();
if (freeAllocation == null)
{
await AlertService.Error(
SmartTranslateService.Translate("No free allocation found"));
return;
}
Server.Allocations.Add(freeAllocation);
ServerRepository.Update(Server);
await InvokeAsync(StateHasChanged);
}
private async Task DeleteAllocation(NodeAllocation nodeAllocation)
{
Server.Allocations.Remove(nodeAllocation);
ServerRepository.Update(Server);
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -33,6 +33,7 @@
</Route>
<Route Path="/allocations">
<AdminServersViewNavigation Index="3" Server="Server"/>
<Allocations />
</Route>
<Route Path="/archive">
<AdminServersViewNavigation Index="4" Server="Server"/>
@@ -72,6 +73,7 @@
.Include(x => x.Allocations)
.Include(x => x.MainAllocation)
.Include(x => x.Variables)
.Include(x => x.Node)
.FirstOrDefault(x => x.Id == Id);
return Task.CompletedTask;

View File

@@ -3,37 +3,18 @@
@using BlazorTable
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Moonlight.App.Services.LogServices
@using Moonlight.Shared.Components.Navigations
@inject LogService LogService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<AdminSystemNavigation Index="1"/>
<div class="card">
<div class="card-body">
<LazyLoader Load="Load">
<Table TableItem="LogEntry" Items="LogEntries" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="false"></Column>
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log level"))" Field="@(x => x.Level)" Sortable="true" Filterable="false"></Column>
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log message"))" Field="@(x => x.Message)" Sortable="false" Filterable="true"></Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private LogEntry[] LogEntries;
private Task Load(LazyLoader arg)
{
LogEntries = LogService.GetMessages();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,134 @@
@page "/admin/system/malware"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Background
@using Moonlight.App.Services
@using BlazorTable
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Models.Misc
@inject MalwareScanService MalwareScanService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@implements IDisposable
<OnlyAdmin>
<AdminSystemNavigation Index="2"/>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-body">
@if (MalwareScanService.IsRunning)
{
<span class="fs-3 spinner-border align-middle me-3"></span>
}
<span class="fs-3">@(MalwareScanService.Status)</span>
</div>
<div class="card-footer">
@if (MalwareScanService.IsRunning)
{
<button class="btn btn-success disabled">
<TL>Scan in progress</TL>
</button>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
CssClasses="btn-success"
OnClick="MalwareScanService.Start">
</WButton>
}
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Results</TL>
</span>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
<div class="table-responsive">
<Table TableItem="Server" Items="ScanResults.Keys" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/server/@(context.Uuid)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Results"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<div class="row">
@foreach (var result in ScanResults[context])
{
<div class="col-12 col-md-6 p-3">
<div class="accordion" id="scanResult@(result.GetHashCode())">
<div class="accordion-item">
<h2 class="accordion-header" id="scanResult-header@(result.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scanResult-body@(result.GetHashCode())" aria-expanded="false" aria-controls="scanResult-body@(result.GetHashCode())">
<span>@(result.Title)</span>
</button>
</h2>
<div id="scanResult-body@(result.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="scanResult-header@(result.GetHashCode())" data-bs-parent="#scanResult">
<div class="accordion-body">
<p>
@(result.Description)
</p>
</div>
</div>
</div>
</div>
</div>
}
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
</OnlyAdmin>
@code
{
private readonly Dictionary<Server, MalwareScanResult[]> ScanResults = new();
private LazyLoader LazyLoaderResults;
protected override async Task OnInitializedAsync()
{
await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
await Event.On<Object>("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); });
}
private Task LoadResults(LazyLoader arg)
{
ScanResults.Clear();
lock (MalwareScanService.ScanResults)
{
foreach (var result in MalwareScanService.ScanResults)
{
ScanResults.Add(result.Key, result.Value);
}
}
return Task.CompletedTask;
}
public async void Dispose()
{
await Event.Off("malwareScan.status", this);
await Event.Off("malwareScan.result", this);
}
}

View File

@@ -3,7 +3,6 @@
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Sessions
@using BlazorTable
@using Logging.Net
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services

View File

@@ -5,6 +5,8 @@
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@using Mappy.Net
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@inject UserRepository UserRepository
@@ -13,7 +15,7 @@
<LazyLoader Load="Load">
<SmartForm OnValidSubmit="Save" Model="Model">
<div class="card mb-5 mb-xl-10">
<div class="card-body p-9">
<div class="card-body p-9 @(CurrentUser.StreamerMode ? "blur" : "")">
<div class="row">
<div class="col-lg-6 fv-row fv-plugins-icon-container">
<div class="mb-3">
@@ -74,7 +76,7 @@
@code
{
private UserDataModel Model = new UserDataModel();
private UserDataModel Model = new();
[CascadingParameter]
public User CurrentUser { get; set; }
@@ -89,10 +91,21 @@
private Task Save()
{
// Prevent users from locking out other users by changing their email
Model.Email = Model.Email.ToLower();
var userWithThatEmail = UserRepository
.Get()
.FirstOrDefault(x => x.Email == Model.Email);
if (userWithThatEmail != null && CurrentUser.Id != userWithThatEmail.Id)
{
Logger.Warn($"A user tried to lock another user out by changing the email. Email: {Model.Email}", "security");
throw new DisplayException("A user with that email does already exist");
}
CurrentUser = Mapper.Map(CurrentUser, Model);
CurrentUser.Email = CurrentUser.Email.ToLower();
UserRepository.Update(CurrentUser);
return Task.CompletedTask;

View File

@@ -2,319 +2,232 @@
@using Moonlight.Shared.Components.Navigations
@using QRCoder
@using Moonlight.App.Services.LogServices
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using System.Text.RegularExpressions
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Mappy.Net
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@inject SmartTranslateService SmartTranslateService
@inject AuditLogService AuditLogService
@inject TotpService TotpService
@inject NavigationManager NavigationManager
@inject IdentityService IdentityService
@inject UserService UserService
@inject AlertService AlertService
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@inject ModalService ModalService
@inject Repository<User> UserRepository
@inject SmartTranslateService SmartTranslateService
<ProfileNavigation Index="2"/>
<div class="card mb-5 mb-xl-10">
<LazyLoader Load="Load">
@if (TotpEnabled)
{
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
<table class="w-100">
<tr>
<td rowspan="2">
<span class="svg-icon svg-icon-2tx svg-icon-primary">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
</svg>
</span>
</td>
<td class="w-100">
<h4 class="text-gray-900 fw-bold ms-4">
<TL>Your account is secured with 2fa</TL>
</h4>
</td>
<td rowspan="2">
<a @onclick="Disable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
<TL>Disable</TL>
</a>
</td>
</tr>
<tr>
<td>
<div class="fs-6 text-gray-700 pe-7 ms-4">
<TL>anyone write a fancy text here?</TL>
</div>
</td>
</tr>
</table>
<div class="row">
<div class="col-12 col-md-6 p-3">
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Two factor authentication</TL>
</div>
</div>
}
else
{
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
<table class="w-100">
<tr>
<td rowspan="2">
<span class="svg-icon svg-icon-2tx svg-icon-primary">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
</svg>
</span>
</td>
<td class="w-100">
<h4 class="text-gray-900 fw-bold ms-4">
<TL>Secure your account</TL>
</h4>
</td>
<td rowspan="2">
<a @onclick="Enable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
<TL>Enable</TL>
</a>
</td>
</tr>
<tr>
<td>
<div class="fs-6 text-gray-700 pe-7 ms-4">
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
</div>
</td>
</tr>
</table>
<div class="card-body fs-6">
<p>
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
</p>
<div class="d-flex justify-content-center">
@if (User.TotpEnabled)
{
<WButton Text="@(SmartTranslateService.Translate("Disable"))"
WorkingText=""
CssClasses="btn-danger"
OnClick="DisableTwoFactor">
</WButton>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
WorkingText=""
CssClasses="btn-primary"
OnClick="StartTwoFactorWizard">
</WButton>
}
</div>
</div>
}
<div class="modal fade" id="twofactorauth" tabindex="-1" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered mw-650px">
<div class="modal-content">
<div class="modal-header flex-stack py-6">
<h2 class="ms-3">
<TL>Activate 2fa</TL>
</h2>
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
<span class="svg-icon svg-icon-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
</svg>
</span>
</div>
</div>
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
<div>
<h3 class="text-dark fw-bold mb-3 mt-2">
<TL>2fa apps</TL>
</h3>
<div class="text-gray-500 fw-semibold fs-6 mb-10">
<TL>Use an app like </TL>
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>,
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>,
<a href="https://authy.com/download/" target="_blank">Authy</a>, <TL>or</TL>
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a> <TL>and scan the following QR Code</TL>
@if (EnablingTotp)
{
<div class="pt-5 text-center">
@{
QRCodeGenerator qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode
(
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={TotpSecret}&issuer={Uri.EscapeDataString(Issuer)}",
QRCodeGenerator.ECCLevel.Q
);
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
}
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
</div>
}
</div>
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed mb-8 p-6">
<span class="svg-icon svg-icon-2tx svg-icon-warning me-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.3" x="2" y="2" width="20" height="20" rx="10" fill="currentColor"></rect>
<rect x="11" y="14" width="7" height="2" rx="1" transform="rotate(-90 11 14)" fill="currentColor"></rect>
<rect x="11" y="17" width="2" height="2" rx="1" transform="rotate(-90 11 17)" fill="currentColor"></rect>
</svg>
</span>
<div class="d-flex flex-stack flex-grow-1">
<div class="fw-semibold">
<div class="fs-6 text-gray-700">
<TL>If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:</TL>
<div class="fw-bold text-dark pt-2">@(TotpSecret)</div>
</div>
</div>
</div>
</div>
<a class="btn btn-primary px-6 align-self-center text-nowrap float-end" data-bs-toggle="modal" data-bs-target="#test">
<TL>Next</TL>
</a>
</div>
</div>
</div>
<div class="col-12 col-md-6 p-3">
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Password</TL>
</div>
</div>
<div class="card-body fs-6">
<div class="d-flex justify-content-center">
<div class="input-group">
<input @bind="Password" class="form-control" type="password"/>
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
WorkingText=""
CssClasses="btn-primary"
OnClick="ChangePassword">
</WButton>
</div>
</div>
</div>
</div>
<div class="modal fade" id="test" tabindex="-1" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered mw-650px">
<div class="modal-content">
<div class="modal-header flex-stack py-6">
<h2 class="ms-3">
<TL>Finish activation</TL>
</h2>
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
<span class="svg-icon svg-icon-1">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
</svg>
</span>
</div>
</div>
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
<div class="text-gray-500 fw-semibold fs-6 mb-10">
<div class="alert alert-primary d-flex align-items-center p-5 mb-6">
<i class="bx bx-info-circle fs-2hx text-primary me-4">
<span class="path1"></span><span class="path2"></span>
</i>
<div class="d-flex flex-column">
<h4 class="mb-1 text-primary">
<TL>2fa Code requiered</TL>
</h4>
<span>In order to finish the activation of 2fa, you need to enter the code your 2fa app shows you.</span>
</div>
</div>
<input type="text" class="form-control form-control-lg form-control-solid mb-0" placeholder="@SmartTranslateService.Translate("2fa Code")" @bind="currentTotp"/>
<br/>
<WButton CssClasses="btn btn-primary mb-2 align-self-center text-nowrap float-end" WorkingText="@SmartTranslateService.Translate("Saving")" Text="@SmartTranslateService.Translate("Finish")" OnClick="CheckAndSaveTotp">
</WButton>
</div>
</div>
</div>
<div class="col-12 col-md-6 p-3">
<div class="card">
<div class="card-header">
<div class="card-title">
<TL>Preferences</TL>
</div>
</div>
<div class="card-body fs-6">
<div class="form-check form-switch">
<input @bind="UserModel.StreamerMode" class="form-check-input" type="checkbox" role="switch" id="streamerModeSwitch">
<label class="form-check-label" for="streamerModeSwitch">
<TL>Streamer mode</TL>
</label>
</div>
</div>
<div class="card-footer">
<div class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText=""
CssClasses="btn-primary"
OnClick="SavePreferences">
</WButton>
</div>
</div>
</div>
</div>
</div>
@* Modals *@
<div class="modal fade" id="2fa" tabindex="-1" style="display: none" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">
<TL>Activate 2fa</TL>
</h2>
</div>
<div class="modal-body fs-6">
@if (!User.TotpEnabled)
{
if (string.IsNullOrEmpty(User.TotpSecret))
{
<p>
<TL>Make sure you have installed one of the following apps on your smartphone and press continue</TL>
</p>
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>
<br/>
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>
<br/>
<a href="https://authy.com/download/" target="_blank">Authy</a>
<br/>
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a>
<br/>
<div class="separator mt-2"></div>
<div class="alert alert-danger d-flex rounded ms-6 me-6 mt-6 mb-8 bg-body">
<div class="w-100">
<table>
<tr>
<td>
<span class="svg-icon svg-icon-2tx svg-icon-body">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
</svg>
</span>
</td>
<td class="w-25">
<span class="text-gray-700 fw-semibold fs-6 ms-4 me-2">
<TL>New password</TL>
</span>
</td>
<td class="w-75">
<input @bind="Password" type="password" class="form-control">
</td>
<td class="">
<WButton OnClick="ChangePassword"
CssClasses="btn-danger ms-4"
Text="@SmartTranslateService.Translate("Change")"
WorkingText="@SmartTranslateService.Translate("Changing")">
<div class="d-flex justify-content-center">
<WButton Text="@(SmartTranslateService.Translate("Continue"))"
WorkingText="@(SmartTranslateService.Translate("Preparing"))"
CssClasses="btn-primary"
OnClick="GenerateTwoFactorToken">
</WButton>
</td>
</tr>
</table>
</div>
}
else
{
<p>
<TL>Scan the qr code and enter the code generated by the app you have scanned it in</TL>
</p>
<div class="mt-3 text-center">
@{
QRCodeGenerator qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode
(
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={User.TotpSecret}&issuer={Uri.EscapeDataString("Moonlight")}",
QRCodeGenerator.ECCLevel.Q
);
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
}
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
</div>
<div class="mt-3 d-flex justify-content-center">
<div class="input-group">
<input type="text"
@bind="TwoFactorCode"
placeholder="@(SmartTranslateService.Translate("Enter your 2fa code here"))"
class="form-control"/>
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
WorkingText="@(SmartTranslateService.Translate("Processing"))"
CssClasses="btn-primary"
OnClick="EnableTwoFactor">
</WButton>
</div>
</div>
}
}
</div>
</div>
</LazyLoader>
</div>
</div>
@code
{
private bool TotpEnabled = false;
private bool EnablingTotp = false;
private string TotpSecret = "";
private User User;
private string Issuer = "Moonlight";
private string currentTotp = "";
[CascadingParameter]
public User User { get; set; }
private string TwoFactorCode = "";
private string Password = "";
private UserPreferencesDataModel UserModel;
private async void Enable()
protected override void OnParametersSet()
{
await AuditLogService.Log(AuditLogType.EnableTotp, x => x.Add<string>("Totp enabled"));
await TotpService.Enable();
TotpEnabled = await TotpService.GetEnabled();
TotpSecret = await TotpService.GetSecret();
EnablingTotp = true;
StateHasChanged();
UserModel = Mapper.Map<UserPreferencesDataModel>(User);
}
public async Task CheckAndSaveTotp()
private async Task StartTwoFactorWizard()
{
if (await TotpService.Verify(TotpSecret, currentTotp))
{
await TotpService.EnforceTotpLogin();
TotpEnabled = true;
TotpSecret = await TotpService.GetSecret();
await ToastService.Success("Successfully enabled 2fa!");
StateHasChanged();
}
else
{
await AlertService.Error("2fa code incorrect", "The given 2fa code is incorrect. Maybe check if the code in your 2fa app has changed.");
}
await ModalService.Show("2fa");
}
private async void Disable()
private async Task GenerateTwoFactorToken()
{
await AuditLogService.Log(AuditLogType.DisableTotp, x => x.Add<string>("Totp disabled"));
await TotpService.Disable();
await TotpService.GenerateSecret();
await InvokeAsync(StateHasChanged);
}
private async Task EnableTwoFactor()
{
await ModalService.Hide("2fa");
await TotpService.Enable(TwoFactorCode);
await InvokeAsync(StateHasChanged);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
private async Task Load(LazyLoader lazyLoader)
private async Task DisableTwoFactor()
{
await lazyLoader.SetText("Requesting secrets");
TotpEnabled = await TotpService.GetEnabled();
TotpSecret = await TotpService.GetSecret();
await lazyLoader.SetText("Requesting identity");
User = await IdentityService.Get();
await TotpService.Disable();
await InvokeAsync(StateHasChanged);
}
private async Task ChangePassword()
{
if (Regex.IsMatch(Password, @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z@$!%*#.,?&\d]{8,}$"))
{
await UserService.ChangePassword(User, Password);
await UserService.ChangePassword(User, Password);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
await AuditLogService.Log(AuditLogType.PasswordChange, x => x.Add<string>("The password has been set to a new one"));
// Reload to make the user login again
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
else
{
await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number");
}
private async Task SavePreferences()
{
User = Mapper.Map(User, UserModel);
UserRepository.Update(User);
await InvokeAsync(StateHasChanged);
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
}

View File

@@ -2,7 +2,6 @@
@using Task = System.Threading.Tasks.Task
@using Moonlight.App.Repositories.Servers
@using Microsoft.EntityFrameworkCore
@using Logging.Net
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Helpers.Wings
@@ -27,19 +26,7 @@
<LazyLoader Load="LoadData">
@if (CurrentServer == null)
{
<div class="d-flex justify-content-center flex-center">
<div class="card">
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center">
<h1 class="card-title">
<TL>Server not found</TL>
</h1>
<p class="card-text fs-4">
<TL>A server with that id cannot be found or you have no access for this server</TL>
</p>
</div>
</div>
</div>
<NotFoundAlert />
}
else
{
@@ -305,10 +292,6 @@
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
}
}
else
{
Logger.Debug("Server is null");
}
}
private async Task ReconnectConsole()

View File

@@ -2,7 +2,7 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Addon
@using Moonlight.App.ApiClients.Modrinth.Resources
@using Logging.Net
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Interop
@inject ServerAddonPluginService AddonPluginService
@@ -33,14 +33,14 @@
</div>
<div class="card-body">
<LazyLoader @ref="PluginsLazyLoader" Load="LoadPlugins">
@foreach (var pluginsPart in Plugins.Chunk(3))
@if (Plugins.Any())
{
<div class="row">
@foreach (var plugin in pluginsPart)
@foreach (var plugin in Plugins)
{
<div class="col">
<div class="card p-2 card-bordered border-active">
<div class="d-flex justify-content-center">
<div class="col-12 col-md-4 p-3">
<div class="card bg-hover-secondary position-relative">
<div class="d-flex justify-content-center pt-5">
<img height="100" width="100" src="@(plugin.IconUrl)" alt="@(plugin.Title)"/>
</div>
<div class="card-body">
@@ -48,10 +48,14 @@
<p class="card-text">
@(plugin.Description)
</p>
<WButton Text="@(SmartTranslateService.Translate("Install"))"
WorkingText="@(SmartTranslateService.Translate("Installing"))"
CssClasses="btn-primary"
</div>
<div class="position-absolute top-0 end-0 mt-3 me-3">
<a href="https://modrinth.com/plugin/@(plugin.Slug)" class="btn btn-primary p-2" target="_blank">
<i class="ps-1 bx bx-sm bx-link"></i>
</a>
<WButton CssClasses="btn-success p-2"
OnClick="() => InstallPlugin(plugin)">
<i class="ps-1 bx bx-sm bx-download"></i>
</WButton>
</div>
</div>
@@ -59,6 +63,10 @@
}
</div>
}
else
{
<NotFoundAlert />
}
</LazyLoader>
</div>
</div>
@@ -100,7 +108,7 @@ else
if (string.IsNullOrEmpty(version) || version == "latest")
version = "1.20.1"; // This should NOT be called at any time if all the images have the correct tags
Plugins = await AddonPluginService.GetPluginsForVersion(version, PluginsSearch);
}
@@ -117,7 +125,7 @@ else
SmartTranslateService.Translate("Please wait until the other plugin is installed"));
return;
}
IsPluginInstalling = true;
try
@@ -131,13 +139,7 @@ else
await ToastService.CreateProcessToast("pluginDownload", "Preparing");
await AddonPluginService.InstallPlugin(fileAccess, version, project, delegate(string s)
{
Task.Run(async () =>
{
await ToastService.UpdateProcessToast("pluginDownload", s);
});
});
await AddonPluginService.InstallPlugin(fileAccess, version, project, delegate(string s) { Task.Run(async () => { await ToastService.UpdateProcessToast("pluginDownload", s); }); });
await ToastService.Success(
SmartTranslateService.Translate("Successfully installed " + project.Slug)
@@ -145,7 +147,8 @@ else
}
catch (Exception e)
{
Logger.Info(e.Message);
Logger.Warn("Error installing plugin");
Logger.Warn(e);
throw;
}
finally
@@ -154,5 +157,4 @@ else
await ToastService.RemoveProcessToast("pluginDownload");
}
}
}
}

View File

@@ -1,6 +1,5 @@
@using Moonlight.App.Services
@using Moonlight.App.Helpers
@using Logging.Net
@using BlazorContextMenu
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events

View File

@@ -42,15 +42,12 @@
</div>
</div>
</div>
<div class="row">
<div class="separator my-5"></div>
</div>
<div class="row">
<div class="row mt-3">
<div class="card card-body">
<div class="row align-items-center">
<div class="col fs-5">
<span class="fw-bold"><TL>Shared IP</TL>:</span>
<span class="ms-1 text-muted">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
<span class="ms-1 text-muted @(User.StreamerMode ? "blur" : "")">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
</div>
<div class="col fs-5">
<span class="fw-bold"><TL>Server ID</TL>:</span>
@@ -107,8 +104,8 @@
</div>
</div>
<div class="mt-5 row">
<div class="d-flex flex-column flex-md-row card card-body p-10">
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column me-5 mb-3 mb-md-0 fs-6 min-w-lg-200px">
<div class="d-flex flex-column flex-md-row card card-body p-5">
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column fs-6 pe-5 mb-5">
<li class="nav-item w-100 me-0 mb-md-2">
<a href="/server/@(CurrentServer.Uuid)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
<i class="bx bx-terminal bx-sm me-2"></i>

View File

@@ -1,7 +1,6 @@
@using Moonlight.App.Repositories
@using Moonlight.Shared.Components.Partials
@using Task = System.Threading.Tasks.Task
@using Logging.Net
@using Moonlight.App.Database.Entities
@inject NodeRepository NodeRepository

View File

@@ -3,18 +3,22 @@
@using Microsoft.AspNetCore.Components.Rendering
<LazyLoader Load="Load">
<div class="accordion" id="serverSetting">
<div class="row">
@foreach (var setting in Settings)
{
<div class="accordion-item">
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="true" aria-controls="serverSetting-body@(setting.GetHashCode())">
<TL>@(setting.Key)</TL>
</button>
</h2>
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
<div class="accordion-body">
@(GetComponent(setting.Value))
<div class="col-12 col-md-6 p-3">
<div class="accordion" id="serverSetting@(setting.GetHashCode())">
<div class="accordion-item">
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="false" aria-controls="serverSetting-body@(setting.GetHashCode())">
<TL>@(setting.Key)</TL>
</button>
</h2>
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
<div class="accordion-body">
@(GetComponent(setting.Value))
</div>
</div>
</div>
</div>
</div>
@@ -31,49 +35,49 @@
public string[] Tags { get; set; }
private Dictionary<string, Type> Settings = new();
private Task Load(LazyLoader lazyLoader)
{
if(Tags.Contains("paperversion"))
if (Tags.Contains("paperversion"))
Settings.Add("Paper version", typeof(PaperVersionSetting));
if(Tags.Contains("forgeversion"))
if (Tags.Contains("forgeversion"))
Settings.Add("Forge version", typeof(ForgeVersionSetting));
if(Tags.Contains("fabricversion"))
if (Tags.Contains("fabricversion"))
Settings.Add("Fabric version", typeof(FabricVersionSetting));
if(Tags.Contains("join2start"))
if (Tags.Contains("join2start"))
Settings.Add("Join2Start", typeof(Join2StartSetting));
if(Tags.Contains("javascriptversion"))
if (Tags.Contains("javascriptversion"))
Settings.Add("Javascript version", typeof(JavascriptVersionSetting));
if(Tags.Contains("javascriptfile"))
if (Tags.Contains("javascriptfile"))
Settings.Add("Javascript file", typeof(JavascriptFileSetting));
if(Tags.Contains("pythonversion"))
if (Tags.Contains("pythonversion"))
Settings.Add("Python version", typeof(PythonVersionSetting));
if(Tags.Contains("javaversion"))
if (Tags.Contains("javaversion"))
Settings.Add("Java version", typeof(JavaRuntimeVersionSetting));
if(Tags.Contains("dotnetversion"))
if (Tags.Contains("dotnetversion"))
Settings.Add("Dotnet version", typeof(DotnetVersionSetting));
if(Tags.Contains("pythonfile"))
if (Tags.Contains("pythonfile"))
Settings.Add("Python file", typeof(PythonFileSetting));
if(Tags.Contains("javafile"))
if (Tags.Contains("javafile"))
Settings.Add("Jar file", typeof(JavaFileSetting));
if(Tags.Contains("dotnetfile"))
if (Tags.Contains("dotnetfile"))
Settings.Add("Dll file", typeof(DotnetFileSetting));
Settings.Add("Rename", typeof(ServerRenameSetting));
Settings.Add("Reset", typeof(ServerResetSetting));
Settings.Add("Delete", typeof(ServerDeleteSetting));
return Task.CompletedTask;

Some files were not shown because too many files have changed in this diff Show More