43 Commits
v1b7 ... 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
Marcel Baumgartner
432e441972 Change moonlight service to always fetch the changelog 2023-06-18 02:37:07 +02:00
Marcel Baumgartner
1dae5150bd Merge pull request #178 from Moonlight-Panel/RewriteNotificationSystem
Rewrite notification system
2023-06-18 02:32:33 +02:00
Marcel Baumgartner
72c6f636ee Rewritten notification system 2023-06-17 20:47:07 +02:00
Daniel Balk
e54d04277d Merge pull request #177 from Moonlight-Panel/ShowMoonlightAppInSessions
added check for moonlight app for getting the device
2023-06-17 17:52:41 +02:00
Daniel Balk
f559f08e8d added check for moonlight app for getting the device 2023-06-17 17:51:34 +02:00
Daniel Balk
2674fb3fa7 Update Debugging.razor 2023-06-17 14:10:24 +02:00
Marcel Baumgartner
d5d77ae7da Merge pull request #175 from Moonlight-Panel/AddMoonlightSideDnsCheckSsl
Added moonlight side dns check for ssl
2023-06-17 13:39:25 +02:00
Marcel Baumgartner
3ae905038c Added moonlight side dns check for ssl 2023-06-17 13:38:32 +02:00
Marcel Baumgartner
6707d722e0 Merge pull request #174 from Moonlight-Panel/SomeHotfixes
Fixed cleanup, error handling missing and missing gif
2023-06-17 13:00:12 +02:00
Marcel Baumgartner
bc9fecf6d9 Fixed cleanup, error handling missing and missing gif 2023-06-17 12:57:01 +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
116 changed files with 4497 additions and 1784 deletions

1
.gitattributes vendored
View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
using System.Data.Common; using System.Data.Common;
using Logging.Net;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Moonlight.App.Helpers;
namespace Moonlight.App.Database.Interceptors; 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") b.Property<int>("Rating")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("ServerListLayoutJson")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State") b.Property<string>("State")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
@@ -787,6 +791,9 @@ namespace Moonlight.App.Database.Migrations
b.Property<int>("Status") b.Property<int>("Status")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("StreamerMode")
.HasColumnType("tinyint(1)");
b.Property<int>("SubscriptionDuration") b.Property<int>("SubscriptionDuration")
.HasColumnType("int"); .HasColumnType("int");

View File

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

View File

@@ -5,6 +5,8 @@ namespace Moonlight.App.Exceptions;
[Serializable] [Serializable]
public class DisplayException : Exception public class DisplayException : Exception
{ {
public bool DoNotTranslate { get; set; } = false;
// //
// For guidelines regarding the creation of new exception types, see // For guidelines regarding the creation of new exception types, see
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
@@ -20,6 +22,11 @@ public class DisplayException : Exception
{ {
} }
public DisplayException(string message, bool doNotTranslate) : base(message)
{
DoNotTranslate = doNotTranslate;
}
public DisplayException(string message, Exception inner) : base(message, inner) public DisplayException(string message, Exception inner) : base(message, inner)
{ {
} }

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 System.Diagnostics;
using Logging.Net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database; using Moonlight.App.Database;
using Moonlight.App.Services; using Moonlight.App.Services;
@@ -82,19 +81,32 @@ public class DatabaseCheckupService
Logger.Info($"Saving it to: {file}"); Logger.Info($"Saving it to: {file}");
Logger.Info("Starting backup..."); Logger.Info("Starting backup...");
var sw = new Stopwatch(); try
sw.Start(); {
var sw = new Stopwatch();
sw.Start();
await using MySqlConnection conn = new MySqlConnection(connectionString); await using MySqlConnection conn = new MySqlConnection(connectionString);
await using MySqlCommand cmd = new MySqlCommand(); await using MySqlCommand cmd = new MySqlCommand();
using MySqlBackup mb = new MySqlBackup(cmd); using MySqlBackup mb = new MySqlBackup(cmd);
cmd.Connection = conn; cmd.Connection = conn;
await conn.OpenAsync(); await conn.OpenAsync();
mb.ExportToFile(file); mb.ExportToFile(file);
await conn.CloseAsync(); await conn.CloseAsync();
sw.Stop(); sw.Stop();
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s"); 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; using ConnectionInfo = Renci.SshNet.ConnectionInfo;
namespace Moonlight.App.Helpers.Files; namespace Moonlight.App.Helpers.Files;

View File

@@ -1,6 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Logging.Net;
namespace Moonlight.App.Helpers; 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 System.ComponentModel.DataAnnotations;
using Logging.Net;
namespace Moonlight.App.Helpers; 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 public static class ParseHelper
{ {

View File

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

View File

@@ -2,145 +2,166 @@
using System.Text; using System.Text;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Notification; using Moonlight.App.Database.Entities.Notification;
using Moonlight.App.Models.Notifications; using Moonlight.App.Models.Notifications;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services; using Moonlight.App.Services;
using Moonlight.App.Services.Notifications; using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.Sessions;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Moonlight.App.Http.Controllers.Api.Moonlight.Notifications; namespace Moonlight.App.Http.Controllers.Api.Moonlight.Notifications;
public class ListenController : ControllerBase [ApiController]
[Route("api/moonlight/notification/listen")]
public class ListenController : Controller
{ {
internal WebSocket ws; private WebSocket WebSocket;
private bool active = true;
private bool isAuth = false;
private NotificationClient Client; private NotificationClient Client;
private CancellationTokenSource CancellationTokenSource = new();
private User? CurrentUser;
private readonly IdentityService IdentityService;
private readonly NotificationRepository NotificationRepository;
private readonly OneTimeJwtService OneTimeJwtService; private readonly OneTimeJwtService OneTimeJwtService;
private readonly NotificationClientService NotificationClientService;
private readonly NotificationServerService NotificationServerService; private readonly NotificationServerService NotificationServerService;
private readonly Repository<NotificationClient> NotificationClientRepository;
public ListenController(IdentityService identityService, public ListenController(
NotificationRepository notificationRepository,
OneTimeJwtService oneTimeJwtService, OneTimeJwtService oneTimeJwtService,
NotificationClientService notificationClientService, NotificationServerService notificationServerService, Repository<NotificationClient> notificationClientRepository)
NotificationServerService notificationServerService)
{ {
IdentityService = identityService;
NotificationRepository = notificationRepository;
OneTimeJwtService = oneTimeJwtService; OneTimeJwtService = oneTimeJwtService;
NotificationClientService = notificationClientService;
NotificationServerService = notificationServerService; NotificationServerService = notificationServerService;
NotificationClientRepository = notificationClientRepository;
} }
[Route("/api/moonlight/notifications/listen")] [Route("/api/moonlight/notifications/listen")]
public async Task Get() public async Task<ActionResult> Get()
{ {
if (HttpContext.WebSockets.IsWebSocketRequest) if (HttpContext.WebSockets.IsWebSocketRequest)
{ {
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); WebSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
ws = webSocket;
await Echo(); await ProcessWebsocket();
return new EmptyResult();
} }
else else
{ {
HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden; return StatusCode(400);
} }
} }
private async Task Echo() private async Task ProcessWebsocket()
{ {
while (active) while (!CancellationTokenSource.Token.IsCancellationRequested && WebSocket.State == WebSocketState.Open)
{ {
byte[] bytes = new byte[1024 * 16]; try
var asg = new ArraySegment<byte>(bytes);
var res = await ws.ReceiveAsync(asg, CancellationToken.None);
var text = Encoding.UTF8.GetString(bytes).Trim('\0');
var obj = JsonConvert.DeserializeObject<BasicWSModel>(text);
if (!string.IsNullOrWhiteSpace(obj.Action))
{ {
await HandleRequest(text, obj.Action); byte[] buffer = new byte[1024 * 16];
} _ = await WebSocket.ReceiveAsync(buffer, CancellationTokenSource.Token);
var text = Encoding.UTF8.GetString(buffer).Trim('\0');
active = ws.State == WebSocketState.Open; var basicWsModel = JsonConvert.DeserializeObject<BasicWSModel>(text) ?? new();
if (!string.IsNullOrWhiteSpace(basicWsModel.Action))
{
await HandleRequest(text, basicWsModel.Action);
}
if (WebSocket.State != WebSocketState.Open)
{
CancellationTokenSource.Cancel();
}
}
catch (WebSocketException e)
{
CancellationTokenSource.Cancel();
}
} }
await NotificationServerService.UnRegisterClient(Client);
} }
private async Task HandleRequest(string text, string action) private async Task HandleRequest(string text, string action)
{ {
if (!isAuth && action == "login") if (CurrentUser == null && action != "login")
await Login(text);
else if (!isAuth)
await ws.SendAsync(Encoding.UTF8.GetBytes("{\"error\": \"Unauthorised\"}"), WebSocketMessageType.Text,
WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
else switch (action)
{ {
await Send("{\"error\": \"Unauthorised\"}");
}
switch (action)
{
case "login":
await Login(text);
break;
case "received": case "received":
await Received(text); await Received(text);
break; break;
case "read": case "read":
await Read(text); await Read(text);
break; break;
default:
break;
} }
} }
private async Task Send(string text)
{
await WebSocket.SendAsync(
Encoding.UTF8.GetBytes(text),
WebSocketMessageType.Text,
WebSocketMessageFlags.EndOfMessage, CancellationTokenSource.Token
);
}
private async Task Login(string json) private async Task Login(string json)
{ {
var jwt = JsonConvert.DeserializeObject<Login>(json).token; var loginModel = JsonConvert.DeserializeObject<Login>(json) ?? new();
var dict = await OneTimeJwtService.Validate(jwt); var dict = await OneTimeJwtService.Validate(loginModel.Token);
if (dict == null) if (dict == null)
{ {
string error = "{\"status\":false}"; await Send("{\"status\":false}");
var bytes = Encoding.UTF8.GetBytes(error);
await ws.SendAsync(bytes, WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
return; return;
} }
var _clientId = dict["clientId"]; if (!int.TryParse(dict["clientId"], out int clientId))
var clientId = int.Parse(_clientId); {
await Send("{\"status\":false}");
return;
}
var client = NotificationRepository.GetClients().Include(x => x.User).First(x => x.Id == clientId); Client = NotificationClientRepository
.Get()
.Include(x => x.User)
.First(x => x.Id == clientId);
Client = client; CurrentUser = Client.User;
await InitWebsocket();
string success = "{\"status\":true}"; await NotificationServerService.RegisterClient(WebSocket, Client);
var byt = Encoding.UTF8.GetBytes(success);
await ws.SendAsync(byt, WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
}
private async Task InitWebsocket() await Send("{\"status\":true}");
{
NotificationClientService.listenController = this;
NotificationClientService.WebsocketReady(Client);
isAuth = true;
} }
private async Task Received(string json) private async Task Received(string json)
{ {
var id = JsonConvert.DeserializeObject<NotificationById>(json).notification; var id = JsonConvert.DeserializeObject<NotificationById>(json).Notification;
//TODO: Implement ws notification received //TODO: Implement ws notification received
} }
private async Task Read(string json) private async Task Read(string json)
{ {
var id = JsonConvert.DeserializeObject<NotificationById>(json).notification; var model = JsonConvert.DeserializeObject<NotificationById>(json) ?? new();
await NotificationServerService.SendAction(NotificationClientService.User, await NotificationServerService.SendAction(
JsonConvert.SerializeObject(new NotificationById() {Action = "hide", notification = id})); CurrentUser!,
JsonConvert.SerializeObject(
new NotificationById()
{
Action = "hide", Notification = model.Notification
}
)
);
} }
} }

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;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;

View File

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

View File

@@ -1,5 +1,4 @@
using Logging.Net; using Moonlight.App.Helpers;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Moonlight.App.LogMigrator; 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,19 @@
using System.Net.WebSockets;
using System.Text;
using Moonlight.App.Database.Entities.Notification;
namespace Moonlight.App.Models.Misc;
public class ActiveNotificationClient
{
public WebSocket WebSocket { get; set; }
public NotificationClient Client { get; set; }
public async Task SendAction(string action)
{
await WebSocket.SendAsync(
Encoding.UTF8.GetBytes(action),
WebSocketMessageType.Text,
WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
}
}

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,6 +1,8 @@
namespace Moonlight.App.Models.Notifications; using Newtonsoft.Json;
namespace Moonlight.App.Models.Notifications;
public class Login : BasicWSModel public class Login : BasicWSModel
{ {
public string token { get; set; } [JsonProperty("token")] public string Token { get; set; } = "";
} }

View File

@@ -1,6 +1,9 @@
namespace Moonlight.App.Models.Notifications; using Newtonsoft.Json;
namespace Moonlight.App.Models.Notifications;
public class NotificationById : BasicWSModel public class NotificationById : BasicWSModel
{ {
public int notification { get; set; } [JsonProperty("notification")]
public int Notification { get; set; }
} }

View File

@@ -1,5 +1,4 @@
using System.Text; using System.Text;
using Logging.Net;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
@@ -87,6 +86,13 @@ public class DiscordOAuth2Provider : OAuth2Provider
var email = getData.GetValue<string>("email"); var email = getData.GetValue<string>("email");
var id = getData.GetValue<ulong>("id"); 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 // Handle data

View File

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

View File

@@ -1,5 +1,4 @@
using Logging.Net; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MineStatLib; using MineStatLib;
using Moonlight.App.ApiClients.Daemon.Resources; using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings; using Moonlight.App.ApiClients.Wings;
@@ -46,13 +45,11 @@ public class CleanupService
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
/* if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode)
* if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode)
{ {
Logger.Info("Disabling cleanup service"); Logger.Info("Disabling cleanup service");
return; return;
} }
*/
Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait"))); Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait")));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,19 @@
using CloudFlare.Client; using CloudFlare.Client;
using CloudFlare.Client.Api.Authentication; using CloudFlare.Client.Api.Authentication;
using CloudFlare.Client.Api.Display;
using CloudFlare.Client.Api.Parameters.Data; using CloudFlare.Client.Api.Parameters.Data;
using CloudFlare.Client.Api.Result; using CloudFlare.Client.Api.Result;
using CloudFlare.Client.Api.Zones; using CloudFlare.Client.Api.Zones;
using CloudFlare.Client.Api.Zones.DnsRecord; using CloudFlare.Client.Api.Zones.DnsRecord;
using CloudFlare.Client.Enumerators; using CloudFlare.Client.Enumerators;
using Logging.Net;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories.Domains; using Moonlight.App.Repositories.Domains;
using Moonlight.App.Services.LogServices;
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord; using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
using MatchType = CloudFlare.Client.Enumerators.MatchType;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -21,18 +22,15 @@ public class DomainService
private readonly DomainRepository DomainRepository; private readonly DomainRepository DomainRepository;
private readonly SharedDomainRepository SharedDomainRepository; private readonly SharedDomainRepository SharedDomainRepository;
private readonly CloudFlareClient Client; private readonly CloudFlareClient Client;
private readonly AuditLogService AuditLogService;
private readonly string AccountId; private readonly string AccountId;
public DomainService( public DomainService(
ConfigService configService, ConfigService configService,
DomainRepository domainRepository, DomainRepository domainRepository,
SharedDomainRepository sharedDomainRepository, SharedDomainRepository sharedDomainRepository)
AuditLogService auditLogService)
{ {
DomainRepository = domainRepository; DomainRepository = domainRepository;
SharedDomainRepository = sharedDomainRepository; SharedDomainRepository = sharedDomainRepository;
AuditLogService = auditLogService;
var config = configService var config = configService
.GetSection("Moonlight") .GetSection("Moonlight")
@@ -97,9 +95,36 @@ public class DomainService
{ {
var domain = EnsureData(d); var domain = EnsureData(d);
var records = GetData( var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId)
); // 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 rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}"; var dname = $".{rname}";
@@ -149,7 +174,11 @@ public class DomainService
if (dnsRecord.Type == DnsRecordType.Srv) if (dnsRecord.Type == DnsRecordType.Srv)
{ {
var parts = dnsRecord.Name.Split("."); 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(" "); var valueParts = dnsRecord.Content.Split(" ");
@@ -191,11 +220,7 @@ public class DomainService
})); }));
} }
await AuditLogService.Log(AuditLogType.AddDomainRecord, x => //TODO: AuditLog
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
} }
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord) public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
@@ -225,11 +250,7 @@ public class DomainService
})); }));
} }
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, x => //TODO: AuditLog
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
} }
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord) public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
@@ -240,11 +261,7 @@ public class DomainService
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id) await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
); );
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, x => //TODO: AuditLog
{
x.Add<Domain>(d.Id);
x.Add<DnsRecord>(dnsRecord.Name);
});
} }
private Domain EnsureData(Domain domain) 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; 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.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;

View File

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

View File

@@ -1,4 +1,4 @@
using Logging.Net; using Moonlight.App.Helpers;
using Octokit; using Octokit;
using Repository = LibGit2Sharp.Repository; using Repository = LibGit2Sharp.Repository;
@@ -33,9 +33,6 @@ public class MoonlightService
private async Task FetchChangeLog() private async Task FetchChangeLog()
{ {
if(AppVersion == "unknown")
return;
if (ConfigService.DebugMode) if (ConfigService.DebugMode)
{ {
ChangeLog.Add(new[] ChangeLog.Add(new[]

View File

@@ -1,43 +0,0 @@
using System.Net.WebSockets;
using System.Text;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Notification;
using Moonlight.App.Http.Controllers.Api.Moonlight.Notifications;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.Notifications;
public class NotificationClientService
{
private readonly NotificationRepository NotificationRepository;
private readonly NotificationServerService NotificationServerService;
internal ListenController listenController;
public NotificationClientService(NotificationRepository notificationRepository, NotificationServerService notificationServerService)
{
NotificationRepository = notificationRepository;
NotificationServerService = notificationServerService;
}
public User User => NotificationClient.User;
public NotificationClient NotificationClient { get; set; }
public async Task SendAction(string action)
{
await listenController.ws.SendAsync(Encoding.UTF8.GetBytes(action), WebSocketMessageType.Text,
WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
}
public void WebsocketReady(NotificationClient client)
{
NotificationClient = client;
NotificationServerService.AddClient(this);
}
public void WebsocketClosed()
{
NotificationServerService.RemoveClient(this);
}
}

View File

@@ -1,84 +1,113 @@
using Microsoft.EntityFrameworkCore; using System.Net.WebSockets;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Notification; using Moonlight.App.Database.Entities.Notification;
using Moonlight.App.Events;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
namespace Moonlight.App.Services.Notifications; namespace Moonlight.App.Services.Notifications;
public class NotificationServerService public class NotificationServerService
{ {
private UserRepository UserRepository; private readonly List<ActiveNotificationClient> ActiveClients = new();
private NotificationRepository NotificationRepository;
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
private IServiceScope ServiceScope; private readonly EventSystem Event;
public NotificationServerService(IServiceScopeFactory serviceScopeFactory) public NotificationServerService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
Task.Run(Run); Event = eventSystem;
} }
private Task Run() public Task<ActiveNotificationClient[]> GetActiveClients()
{ {
ServiceScope = ServiceScopeFactory.CreateScope(); lock (ActiveClients)
{
UserRepository = ServiceScope return Task.FromResult(ActiveClients.ToArray());
.ServiceProvider }
.GetRequiredService<UserRepository>();
NotificationRepository = ServiceScope
.ServiceProvider
.GetRequiredService<NotificationRepository>();
return Task.CompletedTask;
} }
private List<NotificationClientService> connectedClients = new(); public Task<ActiveNotificationClient[]> GetUserClients(User user)
public List<NotificationClientService> GetConnectedClients()
{ {
return connectedClients.ToList(); lock (ActiveClients)
} {
return Task.FromResult(
public List<NotificationClientService> GetConnectedClients(User user) ActiveClients
{ .Where(x => x.Client.User.Id == user.Id)
return connectedClients.Where(x => x.User == user).ToList(); .ToArray()
);
}
} }
public async Task SendAction(User user, string action) public async Task SendAction(User user, string action)
{ {
var clients = NotificationRepository.GetClients().Include(x => x.User).Where(x => x.User == user).ToList(); using var scope = ServiceScopeFactory.CreateScope();
var notificationClientRepository =
scope.ServiceProvider.GetRequiredService<Repository<NotificationClient>>();
var clients = notificationClientRepository
.Get()
.Include(x => x.User)
.Where(x => x.User == user)
.ToList();
foreach (var client in clients) foreach (var client in clients)
{ {
var notificationAction = new NotificationAction() ActiveNotificationClient[] connectedUserClients;
{
Action = action,
NotificationClient = client
};
var connected = connectedClients.Where(x => x.NotificationClient.Id == client.Id).ToList(); lock (ActiveClients)
if (connected.Count > 0)
{ {
var clientService = connected[0]; connectedUserClients = ActiveClients
await clientService.SendAction(action); .Where(x => x.Client.Id == user.Id)
.ToArray();
}
if (connectedUserClients.Length > 0)
{
await connectedUserClients[0].SendAction(action);
} }
else else
{ {
NotificationRepository.AddAction(notificationAction); var notificationAction = new NotificationAction()
{
Action = action,
NotificationClient = client
};
var notificationActionsRepository =
scope.ServiceProvider.GetRequiredService<Repository<NotificationAction>>();
notificationActionsRepository.Add(notificationAction);
} }
} }
} }
public void AddClient(NotificationClientService notificationClientService) public async Task RegisterClient(WebSocket webSocket, NotificationClient notificationClient)
{ {
connectedClients.Add(notificationClientService); var newClient = new ActiveNotificationClient()
{
WebSocket = webSocket,
Client = notificationClient
};
lock (ActiveClients)
{
ActiveClients.Add(newClient);
}
await Event.Emit("notifications.addClient", notificationClient);
} }
public void RemoveClient(NotificationClientService notificationClientService) public async Task UnRegisterClient(NotificationClient client)
{ {
connectedClients.Remove(notificationClientService); lock (ActiveClients)
{
ActiveClients.RemoveAll(x => x.Client == client);
}
await Event.Emit("notifications.removeClient", client);
} }
} }

View File

@@ -4,9 +4,7 @@ using JWT.Builder;
using JWT.Exceptions; using JWT.Exceptions;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -14,15 +12,12 @@ public class OneTimeJwtService
{ {
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly RevokeRepository RevokeRepository; private readonly RevokeRepository RevokeRepository;
private readonly SecurityLogService SecurityLogService;
public OneTimeJwtService(ConfigService configService, public OneTimeJwtService(ConfigService configService,
RevokeRepository revokeRepository, RevokeRepository revokeRepository)
SecurityLogService securityLogService)
{ {
ConfigService = configService; ConfigService = configService;
RevokeRepository = revokeRepository; RevokeRepository = revokeRepository;
SecurityLogService = securityLogService;
} }
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null) public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
@@ -76,10 +71,7 @@ public class OneTimeJwtService
} }
catch (SignatureVerificationException) catch (SignatureVerificationException)
{ {
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x => Logger.Warn($"Detected a manipulated JWT: {token}", "security");
{
x.Add<string>(token);
});
return null; return null;
} }
catch (Exception e) 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;
using Moonlight.App.ApiClients.Wings.Requests; using Moonlight.App.ApiClients.Wings.Requests;
using Moonlight.App.ApiClients.Wings.Resources; using Moonlight.App.ApiClients.Wings.Resources;
@@ -13,7 +12,6 @@ using Moonlight.App.Helpers.Wings;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers; using Moonlight.App.Repositories.Servers;
using Moonlight.App.Services.LogServices;
using FileAccess = Moonlight.App.Helpers.Files.FileAccess; using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -30,9 +28,6 @@ public class ServerService
private readonly UserService UserService; private readonly UserService UserService;
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly WingsJwtHelper WingsJwtHelper; private readonly WingsJwtHelper WingsJwtHelper;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService;
private readonly NodeService NodeService; private readonly NodeService NodeService;
private readonly DateTimeService DateTimeService; private readonly DateTimeService DateTimeService;
private readonly EventSystem Event; private readonly EventSystem Event;
@@ -46,9 +41,6 @@ public class ServerService
UserService userService, UserService userService,
ConfigService configService, ConfigService configService,
WingsJwtHelper wingsJwtHelper, WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ErrorLogService errorLogService,
NodeService nodeService, NodeService nodeService,
NodeAllocationRepository nodeAllocationRepository, NodeAllocationRepository nodeAllocationRepository,
DateTimeService dateTimeService, DateTimeService dateTimeService,
@@ -63,9 +55,6 @@ public class ServerService
UserService = userService; UserService = userService;
ConfigService = configService; ConfigService = configService;
WingsJwtHelper = wingsJwtHelper; WingsJwtHelper = wingsJwtHelper;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
ErrorLogService = errorLogService;
NodeService = nodeService; NodeService = nodeService;
NodeAllocationRepository = nodeAllocationRepository; NodeAllocationRepository = nodeAllocationRepository;
DateTimeService = dateTimeService; DateTimeService = dateTimeService;
@@ -107,11 +96,7 @@ public class ServerService
Action = rawSignal Action = rawSignal
}); });
await AuditLogService.Log(AuditLogType.ChangePowerState, x => //TODO: AuditLog
{
x.Add<Server>(server.Uuid);
x.Add<PowerSignal>(rawSignal);
});
} }
public async Task<ServerBackup> CreateBackup(Server server) public async Task<ServerBackup> CreateBackup(Server server)
@@ -140,12 +125,7 @@ public class ServerService
Ignore = "" Ignore = ""
}); });
await AuditLogService.Log(AuditLogType.CreateBackup, //TODO: AuditLog
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
});
return backup; return backup;
} }
@@ -182,12 +162,7 @@ public class ServerService
Adapter = "wings" Adapter = "wings"
}); });
await AuditLogService.Log(AuditLogType.RestoreBackup, //TODO: AuditLog
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
} }
public async Task DeleteBackup(Server server, ServerBackup serverBackup) public async Task DeleteBackup(Server server, ServerBackup serverBackup)
@@ -220,13 +195,7 @@ public class ServerService
await Event.Emit("wings.backups.delete", backup); await Event.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup, //TODO: AuditLog
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
}
);
} }
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup) public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
@@ -239,12 +208,7 @@ public class ServerService
claims.Add("backup_uuid", serverBackup.Uuid.ToString()); claims.Add("backup_uuid", serverBackup.Uuid.ToString());
}); });
await AuditLogService.Log(AuditLogType.DownloadBackup, //TODO: AuditLog
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
if (server.Node.Ssl) if (server.Node.Ssl)
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}"; return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
@@ -346,17 +310,14 @@ public class ServerService
StartOnCompletion = false StartOnCompletion = false
}); });
await AuditLogService.Log(AuditLogType.CreateServer, x => { x.Add<Server>(newServerData.Uuid); }); //TODO: AuditLog
return newServerData; return newServerData;
} }
catch (Exception e) catch (Exception e)
{ {
await ErrorLogService.Log(e, x => Logger.Error("Error creating server on wings");
{ Logger.Error(e);
x.Add<Server>(newServerData.Uuid);
x.Add<Node>(node.Id);
});
ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff
@@ -373,7 +334,7 @@ public class ServerService
server.Installing = true; server.Installing = true;
ServerRepository.Update(server); 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) public async Task<Server> SftpServerLogin(int serverId, int id, string password)
@@ -382,7 +343,7 @@ public class ServerService
if (server == null) 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"); 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; namespace Moonlight.App.Services.Sessions;

View File

@@ -2,12 +2,10 @@
using JWT.Algorithms; using JWT.Algorithms;
using JWT.Builder; using JWT.Builder;
using JWT.Exceptions; using JWT.Exceptions;
using Logging.Net;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using UAParser; using UAParser;
namespace Moonlight.App.Services.Sessions; namespace Moonlight.App.Services.Sessions;
@@ -16,8 +14,6 @@ public class IdentityService
{ {
private readonly UserRepository UserRepository; private readonly UserRepository UserRepository;
private readonly CookieService CookieService; private readonly CookieService CookieService;
private readonly SecurityLogService SecurityLogService;
private readonly ErrorLogService ErrorLogService;
private readonly IHttpContextAccessor HttpContextAccessor; private readonly IHttpContextAccessor HttpContextAccessor;
private readonly string Secret; private readonly string Secret;
@@ -27,15 +23,11 @@ public class IdentityService
CookieService cookieService, CookieService cookieService,
UserRepository userRepository, UserRepository userRepository,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
ConfigService configService, ConfigService configService)
SecurityLogService securityLogService,
ErrorLogService errorLogService)
{ {
CookieService = cookieService; CookieService = cookieService;
UserRepository = userRepository; UserRepository = userRepository;
HttpContextAccessor = httpContextAccessor; HttpContextAccessor = httpContextAccessor;
SecurityLogService = securityLogService;
ErrorLogService = errorLogService;
Secret = configService Secret = configService
.GetSection("Moonlight") .GetSection("Moonlight")
@@ -90,15 +82,13 @@ public class IdentityService
} }
catch (SignatureVerificationException) catch (SignatureVerificationException)
{ {
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, x => Logger.Warn($"Detected a manipulated JWT: {token}", "security");
{
x.Add<string>(token);
});
return null; return null;
} }
catch (Exception e) catch (Exception e)
{ {
await ErrorLogService.Log(e, x => {}); Logger.Error("Error reading jwt");
Logger.Error(e);
return null; return null;
} }
@@ -134,7 +124,8 @@ public class IdentityService
} }
catch (Exception e) catch (Exception e)
{ {
await ErrorLogService.Log(e, x => {}); Logger.Error("Unexpected error while processing token");
Logger.Error(e);
return null; return null;
} }
} }
@@ -159,8 +150,17 @@ public class IdentityService
try try
{ {
var userAgent = HttpContextAccessor.HttpContext.Request.Headers.UserAgent.ToString();
if (userAgent.Contains("Moonlight.App"))
{
var version = userAgent.Remove(0, "Moonlight.App/".Length).Split(' ').FirstOrDefault();
return "Moonlight App " + version;
}
var uaParser = Parser.GetDefault(); var uaParser = Parser.GetDefault();
var info = uaParser.Parse(HttpContextAccessor.HttpContext.Request.Headers.UserAgent); var info = uaParser.Parse(userAgent);
return $"{info.OS} - {info.Device}"; return $"{info.OS} - {info.Device}";
} }

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) catch (Exception ex)
{ {
Logging.Net.Logger.Error(ex); Logger.Error(ex);
return key; return key;
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; using DnsClient;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.CloudPanel; using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.CloudPanel.Requests; using Moonlight.App.ApiClients.CloudPanel.Requests;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
@@ -18,7 +19,8 @@ public class WebSpaceService
private readonly CloudPanelApiHelper CloudPanelApiHelper; private readonly CloudPanelApiHelper CloudPanelApiHelper;
public WebSpaceService(Repository<CloudPanel> cloudPanelRepository, Repository<WebSpace> webSpaceRepository, CloudPanelApiHelper cloudPanelApiHelper, Repository<MySqlDatabase> databaseRepository) public WebSpaceService(Repository<CloudPanel> cloudPanelRepository, Repository<WebSpace> webSpaceRepository,
CloudPanelApiHelper cloudPanelApiHelper, Repository<MySqlDatabase> databaseRepository)
{ {
CloudPanelRepository = cloudPanelRepository; CloudPanelRepository = cloudPanelRepository;
WebSpaceRepository = webSpaceRepository; WebSpaceRepository = webSpaceRepository;
@@ -121,6 +123,52 @@ public class WebSpaceService
{ {
var webspace = EnsureData(w); var webspace = EnsureData(w);
var dns = new LookupClient(new LookupClientOptions()
{
CacheFailedResults = false,
UseCache = false
});
var ipOfWebspaceQuery = await dns.QueryAsync(
webspace.Domain,
QueryType.A
);
var ipOfWebspaceWwwQuery = await dns.QueryAsync(
"www." + webspace.Domain,
QueryType.CNAME
);
var ipOfWebspace = ipOfWebspaceQuery.Answers.ARecords().FirstOrDefault();
var ipOfWebspaceWww = ipOfWebspaceWwwQuery.Answers.CnameRecords().FirstOrDefault();
if (ipOfWebspace == null)
throw new DisplayException($"Unable to find any a records for {webspace.Domain}", true);
if (ipOfWebspaceWww == null)
throw new DisplayException($"Unable to find any cname records for www.{webspace.Domain}", true);
var ipOfHostQuery = await dns.QueryAsync(
webspace.CloudPanel.Host,
QueryType.A
);
var ipOfHost = ipOfHostQuery.Answers.ARecords().FirstOrDefault();
if (ipOfHost == null)
throw new DisplayException("Unable to find a record of host system");
if (ipOfHost.Address.ToString() != ipOfWebspace.Address.ToString())
throw new DisplayException("The dns records of your webspace do not point to the host system");
Logger.Debug($"{ipOfWebspaceWww.CanonicalName.Value}");
if (ipOfWebspaceWww.CanonicalName.Value != webspace.CloudPanel.Host + ".")
throw new DisplayException(
$"The dns record www.{webspace.Domain} does not point to {webspace.CloudPanel.Host}", true);
await CloudPanelApiHelper.Post(webspace.CloudPanel, "letsencrypt/install/certificate", new InstallLetsEncrypt() await CloudPanelApiHelper.Post(webspace.CloudPanel, "letsencrypt/install/certificate", new InstallLetsEncrypt()
{ {
DomainName = webspace.Domain DomainName = webspace.Domain
@@ -180,7 +228,8 @@ public class WebSpaceService
var webspace = EnsureData(w); var webspace = EnsureData(w);
return Task.FromResult<FileAccess>( return Task.FromResult<FileAccess>(
new SftpFileAccess(webspace.CloudPanel.Host, webspace.UserName, webspace.Password, 22, true, $"/htdocs/{webspace.Domain}") new SftpFileAccess(webspace.CloudPanel.Host, webspace.UserName, webspace.Password, 22, true,
$"/htdocs/{webspace.Domain}")
); );
} }

View File

@@ -21,11 +21,11 @@
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" /> <PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
<PackageReference Include="Discord.Net" Version="3.10.0" /> <PackageReference Include="Discord.Net" Version="3.10.0" />
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" /> <PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
<PackageReference Include="DnsClient" Version="1.7.0" />
<PackageReference Include="FluentFTP" Version="46.0.2" /> <PackageReference Include="FluentFTP" Version="46.0.2" />
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" /> <PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
<PackageReference Include="JWT" Version="10.0.2" /> <PackageReference Include="JWT" Version="10.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.27.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="MailKit" Version="4.0.0" />
<PackageReference Include="Mappy.Net" Version="1.0.2" /> <PackageReference Include="Mappy.Net" Version="1.0.2" />
<PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Markdig" Version="0.31.0" />
@@ -46,6 +46,9 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" /> <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="SSH.NET" Version="2020.0.2" />
<PackageReference Include="UAParser" Version="3.1.47" /> <PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="XtermBlazor" Version="1.8.1" /> <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/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.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://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> <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 BlazorTable;
using CurrieTechnologies.Razor.SweetAlert2; using CurrieTechnologies.Razor.SweetAlert2;
using HealthChecks.UI.Client; using HealthChecks.UI.Client;
using Logging.Net;
using Moonlight.App.ApiClients.CloudPanel; using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.Daemon; using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Modrinth; using Moonlight.App.ApiClients.Modrinth;
@@ -24,13 +23,14 @@ using Moonlight.App.Services.Background;
using Moonlight.App.Services.DiscordBot; using Moonlight.App.Services.DiscordBot;
using Moonlight.App.Services.Files; using Moonlight.App.Services.Files;
using Moonlight.App.Services.Interop; using Moonlight.App.Services.Interop;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Mail; using Moonlight.App.Services.Mail;
using Moonlight.App.Services.Minecraft; using Moonlight.App.Services.Minecraft;
using Moonlight.App.Services.Notifications; using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
using Moonlight.App.Services.Statistics; using Moonlight.App.Services.Statistics;
using Moonlight.App.Services.SupportChat; using Moonlight.App.Services.SupportChat;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;
namespace Moonlight namespace Moonlight
{ {
@@ -38,14 +38,31 @@ namespace Moonlight
{ {
public static async Task Main(string[] args) 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($"Working dir: {Directory.GetCurrentDirectory()}");
Logger.Info("Running pre-init tasks"); 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); var databaseCheckupService = new DatabaseCheckupService(configService);
await databaseCheckupService.Perform(); await databaseCheckupService.Perform();
@@ -118,7 +135,6 @@ namespace Moonlight
builder.Services.AddScoped<OneTimeJwtService>(); builder.Services.AddScoped<OneTimeJwtService>();
builder.Services.AddSingleton<NotificationServerService>(); builder.Services.AddSingleton<NotificationServerService>();
builder.Services.AddScoped<NotificationAdminService>(); builder.Services.AddScoped<NotificationAdminService>();
builder.Services.AddScoped<NotificationClientService>();
builder.Services.AddScoped<ModalService>(); builder.Services.AddScoped<ModalService>();
builder.Services.AddScoped<SmartDeployService>(); builder.Services.AddScoped<SmartDeployService>();
builder.Services.AddScoped<WebSpaceService>(); builder.Services.AddScoped<WebSpaceService>();
@@ -135,15 +151,12 @@ namespace Moonlight
builder.Services.AddSingleton<OAuth2Service>(); builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddScoped<DynamicBackgroundService>(); builder.Services.AddScoped<DynamicBackgroundService>();
builder.Services.AddScoped<ServerAddonPluginService>(); builder.Services.AddScoped<ServerAddonPluginService>();
builder.Services.AddScoped<KeyListenerService>();
builder.Services.AddScoped<SubscriptionService>(); builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>(); builder.Services.AddScoped<SubscriptionAdminService>();
// Loggers // Loggers
builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<ErrorLogService>();
builder.Services.AddScoped<LogService>();
builder.Services.AddScoped<MailService>(); builder.Services.AddScoped<MailService>();
builder.Services.AddSingleton<TrashMailDetectorService>(); builder.Services.AddSingleton<TrashMailDetectorService>();
@@ -169,6 +182,7 @@ namespace Moonlight
builder.Services.AddSingleton<StatisticsCaptureService>(); builder.Services.AddSingleton<StatisticsCaptureService>();
builder.Services.AddSingleton<DiscordNotificationService>(); builder.Services.AddSingleton<DiscordNotificationService>();
builder.Services.AddSingleton<CleanupService>(); builder.Services.AddSingleton<CleanupService>();
builder.Services.AddSingleton<MalwareScanService>();
// Other // Other
builder.Services.AddSingleton<MoonlightService>(); builder.Services.AddSingleton<MoonlightService>();
@@ -207,6 +221,7 @@ namespace Moonlight
_ = app.Services.GetRequiredService<DiscordBotService>(); _ = app.Services.GetRequiredService<DiscordBotService>();
_ = app.Services.GetRequiredService<StatisticsCaptureService>(); _ = app.Services.GetRequiredService<StatisticsCaptureService>();
_ = app.Services.GetRequiredService<DiscordNotificationService>(); _ = app.Services.GetRequiredService<DiscordNotificationService>();
_ = app.Services.GetRequiredService<MalwareScanService>();
_ = app.Services.GetRequiredService<MoonlightService>(); _ = app.Services.GetRequiredService<MoonlightService>();

View File

@@ -31,6 +31,20 @@
}, },
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true "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.Interop
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Exceptions @using Moonlight.App.Exceptions
@using Logging.Net
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc @using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Sessions @using Moonlight.App.Services.Sessions
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@inject AlertService AlertService @inject AlertService AlertService
@@ -96,7 +96,7 @@
{ {
<SmartForm Model="TotpData" OnValidSubmit="DoLogin"> <SmartForm Model="TotpData" OnValidSubmit="DoLogin">
<div class="fv-row mb-8 fv-plugins-icon-container"> <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>
<div class="d-grid mb-10"> <div class="d-grid mb-10">
<button type="submit" class="btn btn-primary"> <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 @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
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions @using Moonlight.App.Services.Sessions

View File

@@ -1,11 +1,11 @@
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@using Moonlight.App.Exceptions @using Moonlight.App.Exceptions
@using Moonlight.App.Services @using Moonlight.App.Services
@using Logging.Net
@using Moonlight.App.ApiClients.CloudPanel @using Moonlight.App.ApiClients.CloudPanel
@using Moonlight.App.ApiClients.Daemon @using Moonlight.App.ApiClients.Daemon
@using Moonlight.App.ApiClients.Modrinth @using Moonlight.App.ApiClients.Modrinth
@using Moonlight.App.ApiClients.Wings @using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Helpers
@inherits ErrorBoundaryBase @inherits ErrorBoundaryBase
@inject AlertService AlertService @inject AlertService AlertService
@@ -42,14 +42,23 @@ else
{ {
if (ConfigService.DebugMode) if (ConfigService.DebugMode)
{ {
Logger.Warn(exception); Logger.Verbose(exception);
} }
if (exception is DisplayException displayException) if (exception is DisplayException displayException)
{ {
await AlertService.Error( if (displayException.DoNotTranslate)
SmartTranslateService.Translate(displayException.Message) {
); await AlertService.Error(
displayException.Message
);
}
else
{
await AlertService.Error(
SmartTranslateService.Translate(displayException.Message)
);
}
} }
else if (exception is CloudflareException cloudflareException) else if (exception is CloudflareException cloudflareException)
{ {

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 BlazorMonaco
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Moonlight.Shared.Components.Partials @using Moonlight.Shared.Components.Partials
@inject SmartTranslateService TranslationService @inject SmartTranslateService TranslationService
@inject KeyListenerService KeyListenerService
@inject IJSRuntime JsRuntime @inject IJSRuntime JsRuntime
@implements IDisposable
<div class="card bg-black rounded"> <div class="card bg-black rounded">
<div class="card-body"> <div class="card-body">
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/> <MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
@@ -65,6 +70,16 @@
}, },
AutoIndent = true 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) protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -111,4 +126,10 @@
{ {
return await Editor.GetValue(); 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.Files
@using Moonlight.App.Helpers @using Moonlight.App.Helpers
@using Logging.Net
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@using BlazorDownloadFile @using BlazorDownloadFile
@@ -17,7 +16,7 @@
InitialData="@EditorInitialData" InitialData="@EditorInitialData"
Language="@EditorLanguage" Language="@EditorLanguage"
OnCancel="() => Cancel()" OnCancel="() => Cancel()"
OnSubmit="(_) => Cancel(true)" OnSubmit="(_) => Save()"
HideControls="false"> HideControls="false">
</FileEditor> </FileEditor>
} }
@@ -27,7 +26,7 @@ else
<div class="card-header border-0 my-2"> <div class="card-header border-0 my-2">
<div class="card-title"> <div class="card-title">
<div class="d-flex flex-stack"> <div class="d-flex flex-stack">
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" /> <FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
</div> </div>
</div> </div>
<div class="card-toolbar"> <div class="card-toolbar">
@@ -35,7 +34,9 @@ else
@if (View != null && View.SelectedFiles.Any()) @if (View != null && View.SelectedFiles.Any())
{ {
<div class="fw-bold me-5"> <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> </div>
<WButton Text="@(SmartTranslateService.Translate("Move"))" <WButton Text="@(SmartTranslateService.Translate("Move"))"
@@ -58,37 +59,46 @@ else
} }
else else
{ {
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3"> <div class="btn-group me-3">
<span class="svg-icon svg-icon-muted svg-icon-2"> <button type="button" @onclick="Launch" class="btn btn-light-primary">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <TL>Launch WinSCP</TL>
<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"/> </button>
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/> <button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
</svg> <span class="visually-hidden"></span>
</span> </button>
<TL>Launch WinSCP</TL> <ul class="dropdown-menu">
</button> <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"> <div class="btn-group me-3">
<span class="svg-icon svg-icon-2"> <button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <TL>New</TL>&nbsp;
<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> </button>
</svg> <ul class="dropdown-menu">
</span> <li>
<TL>New file</TL> <button @onclick="CreateFile" class="dropdown-item btn">
</button> <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"> <FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
<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" />
} }
</div> </div>
</div> </div>
@@ -111,6 +121,8 @@ else
Access="MoveAccess" Access="MoveAccess"
OnSubmit="OnFileMoveSubmit"> OnSubmit="OnFileMoveSubmit">
</FileSelectModal> </FileSelectModal>
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
} }
@code @code
@@ -136,6 +148,9 @@ else
// Config // Config
private ContextAction[] Actions = Array.Empty<ContextAction>(); private ContextAction[] Actions = Array.Empty<ContextAction>();
// Connection details
private ConnectionDetailsModal ConnectionDetailsModal;
protected override void OnInitialized() protected override void OnInitialized()
{ {
MoveAccess = (FileAccess)Access.Clone(); MoveAccess = (FileAccess)Access.Clone();
@@ -152,7 +167,7 @@ else
SmartTranslateService.Translate("Rename"), SmartTranslateService.Translate("Rename"),
SmartTranslateService.Translate("Enter a new name"), SmartTranslateService.Translate("Enter a new name"),
x.Name x.Name
); );
if (name != x.Name) if (name != x.Name)
{ {
@@ -163,7 +178,7 @@ else
} }
}); });
actions.Add(new () actions.Add(new()
{ {
Id = "download", Id = "download",
Name = "Download", Name = "Download",
@@ -201,7 +216,7 @@ else
} }
}); });
actions.Add(new () actions.Add(new()
{ {
Id = "decompress", Id = "decompress",
Name = "Decompress", Name = "Decompress",
@@ -255,6 +270,13 @@ else
return false; 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) private async void Cancel(bool save = false)
{ {
if (save) if (save)
@@ -299,7 +321,7 @@ else
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
return; return;
await Access.Write(new FileData{IsFile = true, Name = name}, ""); await Access.Write(new FileData { IsFile = true, Name = name }, "");
await View!.Refresh(); await View!.Refresh();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,8 +15,8 @@
</a> </a>
</li> </li>
<li class="nav-item mt-2"> <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"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/malware">
<TL>AuditLog</TL> <TL>Malware</TL>
</a> </a>
</li> </li>
<li class="nav-item mt-2"> <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 justify-content-between align-items-start flex-wrap mb-2">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="d-flex align-items-center mb-2"> <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) @if (User.Status == UserStatus.Verified)
{ {
@@ -16,7 +16,7 @@
} }
</div> </div>
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2"> <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) @(User.Email)
</span> </span>
</div> </div>

View File

@@ -69,14 +69,16 @@
</div> </div>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="fw-bold d-flex align-items-center fs-5"> <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) @if (User.Admin)
{ {
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span> <span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
} }
</div> </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> </div>
</div> </div>

View File

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

View File

@@ -21,6 +21,7 @@
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@inject IpBanService IpBanService @inject IpBanService IpBanService
@inject DynamicBackgroundService DynamicBackgroundService @inject DynamicBackgroundService DynamicBackgroundService
@inject KeyListenerService KeyListenerService
@{ @{
var uri = new Uri(NavigationManager.Uri); var uri = new Uri(NavigationManager.Uri);
@@ -40,116 +41,120 @@
} }
} }
<CascadingValue Value="User"> <GlobalErrorBoundary>
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle> <CascadingValue Value="User">
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle>
<div class="d-flex flex-column flex-root app-root" id="kt_app_root"> <div class="d-flex flex-column flex-root app-root" id="kt_app_root">
<div class="app-page flex-column flex-column-fluid" id="kt_app_page"> <div class="app-page flex-column flex-column-fluid" id="kt_app_page">
<canvas id="snow" class="snow-canvas"></canvas> <canvas id="snow" class="snow-canvas"></canvas>
@{ @{
//TODO: Add a way to disable the snow //TODO: Add a way to disable the snow
} }
<PageHeader></PageHeader> <PageHeader></PageHeader>
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper"> <div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
<Sidebar></Sidebar> <Sidebar></Sidebar>
<div class="app-main flex-column flex-row-fluid" id="kt_app_main"> <div class="app-main flex-column flex-row-fluid" id="kt_app_main">
<div class="d-flex flex-column flex-column-fluid"> <div class="d-flex flex-column flex-column-fluid">
<div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')"> <div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')">
<div id="kt_app_content_container" class="app-container container-fluid"> <div id="kt_app_content_container" class="app-container container-fluid">
<div class="mt-10"> <div class="mt-10">
@if (!IsIpBanned) <SoftErrorBoundary>
{ @if (!IsIpBanned)
if (UserProcessed)
{
if (uri.LocalPath != "/login" &&
uri.LocalPath != "/passwordreset" &&
uri.LocalPath != "/register")
{ {
if (User == null) if (UserProcessed)
{ {
<Login></Login> if (uri.LocalPath != "/login" &&
} uri.LocalPath != "/passwordreset" &&
else uri.LocalPath != "/register")
{
if (User.Status == UserStatus.Banned)
{ {
<BannedAlert></BannedAlert> if (User == null)
} {
else if (User.Status == UserStatus.Disabled) <Login></Login>
{ }
<DisabledAlert></DisabledAlert> else
} {
else if (User.Status == UserStatus.PasswordPending) if (User.Status == UserStatus.Banned)
{ {
<PasswordChangeView></PasswordChangeView> <BannedAlert></BannedAlert>
} }
else if (User.Status == UserStatus.DataPending) else if (User.Status == UserStatus.Disabled)
{ {
<UserDataSetView></UserDataSetView> <DisabledAlert></DisabledAlert>
}
else if (User.Status == UserStatus.PasswordPending)
{
<PasswordChangeView></PasswordChangeView>
}
else if (User.Status == UserStatus.DataPending)
{
<UserDataSetView></UserDataSetView>
}
else
{
@Body
<RatingPopup/>
}
}
} }
else else
{ {
@Body if (uri.LocalPath == "/login")
{
<RatingPopup/> <Login></Login>
}
else if (uri.LocalPath == "/register")
{
<Register></Register>
}
else if (uri.LocalPath == "/passwordreset")
{
<PasswordReset></PasswordReset>
}
} }
} }
else
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
</div>
</div>
</div>
</div>
}
} }
else else
{ {
if (uri.LocalPath == "/login") <div class="modal d-block">
{ <div class="modal-dialog modal-dialog-centered mw-900px">
<Login></Login> <div class="modal-content">
} <div class="pt-2 modal-body py-lg-10 px-lg-10">
else if (uri.LocalPath == "/register") <h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
{ <p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
<Register></Register> </div>
}
else if (uri.LocalPath == "/passwordreset")
{
<PasswordReset></PasswordReset>
}
}
}
else
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
</div> </div>
</div> </div>
</div> </div>
</div> }
} </SoftErrorBoundary>
} </div>
else
{
<div class="modal d-block">
<div class="modal-dialog modal-dialog-centered mw-900px">
<div class="modal-content">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
</div>
</div>
</div>
</div>
}
</div> </div>
</div> </div>
</div> </div>
<Footer></Footer>
</div> </div>
<Footer></Footer>
</div> </div>
</div> </div>
</div> </div>
</div> </CascadingValue>
</CascadingValue> </GlobalErrorBoundary>
@code @code
{ {
@@ -203,10 +208,14 @@
UserProcessed = true; UserProcessed = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition"); try
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading"); {
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances"); await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances"); 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(); await SessionService.Register();
@@ -232,6 +241,8 @@
}); });
} }
await KeyListenerService.Initialize();
RunDelayedMenu(0); RunDelayedMenu(0);
RunDelayedMenu(1); RunDelayedMenu(1);
RunDelayedMenu(3); RunDelayedMenu(3);
@@ -248,6 +259,8 @@
{ {
SessionService.Close(); SessionService.Close();
await KeyListenerService.DisposeAsync();
if (User != null) if (User != null)
{ {
await Event.Off($"supportChat.{User.Id}.message", this); await Event.Off($"supportChat.{User.Id}.message", this);

View File

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

View File

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

View File

@@ -1,33 +1,74 @@
@page "/admin/notifications/debugging" @page "/admin/notifications/debugging"
@using Moonlight.App.Services.Notifications @using Moonlight.App.Services.Notifications
@using Moonlight.App.Models.Misc
@using Moonlight.App.Events
@using BlazorTable
@using Moonlight.App.Database.Entities.Notification
@using Moonlight.App.Services
@inject NotificationServerService NotificationServerService @inject NotificationServerService NotificationServerService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@implements IDisposable
<OnlyAdmin> <OnlyAdmin>
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<h1>Notification Debugging</h1> <div class="card card-body">
@foreach (var client in Clients) <Table TableItem="ActiveNotificationClient" Items="Clients" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
{ <Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Client.Id)" Sortable="false" Filterable="true"/>
<hr/> <Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("User"))" Field="@(x => x.Client.User.Email)" Sortable="false" Filterable="true"/>
<div> <Column TableItem="ActiveNotificationClient" Title="" Field="@(x => x.Client.Id)" Sortable="false" Filterable="false">
<p>Id: @client.NotificationClient.Id User: @client.User.Email</p> <Template>
<button @onclick="async () => await SendSampleNotification(client)"></button> <WButton Text="@(SmartTranslateService.Translate("Send notification"))"
</div> WorkingText="@(SmartTranslateService.Translate("Working"))"
} CssClasses="btn-primary"
OnClick="() => SendSampleNotification(context)">
</WButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader> </LazyLoader>
</OnlyAdmin> </OnlyAdmin>
@code { @code
private List<NotificationClientService> Clients; {
private ActiveNotificationClient[] Clients;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Event.On<NotificationClient>("notifications.addClient", this, async client =>
{
Clients = await NotificationServerService.GetActiveClients();
await InvokeAsync(StateHasChanged);
});
await Event.On<NotificationClient>("notifications.removeClient", this, async client =>
{
Clients = await NotificationServerService.GetActiveClients();
await InvokeAsync(StateHasChanged);
});
}
}
private async Task Load(LazyLoader loader) private async Task Load(LazyLoader loader)
{ {
Clients = NotificationServerService.GetConnectedClients(); Clients = await NotificationServerService.GetActiveClients();
} }
private async Task SendSampleNotification(NotificationClientService client) private async Task SendSampleNotification(ActiveNotificationClient client)
{ {
await client.SendAction(@"{""action"": ""notify"",""notification"":{""id"":999,""channel"":""Sample Channel"",""content"":""This is a sample Notification"",""title"":""Sample Notification""}}"); await client.SendAction(@"{""action"": ""notify"",""notification"":{""id"":999,""channel"":""Sample Channel"",""content"":""This is a sample Notification"",""title"":""Sample Notification"",""url"":""server/9b724fe2-d882-49c9-8c34-3414c7e4a17e""}}");
}
public async void Dispose()
{
await Event.Off("notifications.addClient", this);
await Event.Off("notifications.removeClient", this);
} }
} }

View File

@@ -1,13 +1,8 @@
@page "/admin/servers/cleanup" @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.Events
@using Moonlight.App.Services.Background @using Moonlight.App.Services.Background
@inject CleanupService CleanupService @inject CleanupService CleanupService
@inject AuditLogService AuditLogService
@inject EventSystem Event @inject EventSystem Event
@implements IDisposable @implements IDisposable

View File

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

View File

@@ -4,8 +4,8 @@
@using Moonlight.App.Services @using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Moonlight.App.Exceptions @using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@using Logging.Net
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@inject NodeRepository NodeRepository @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>
<Route Path="/allocations"> <Route Path="/allocations">
<AdminServersViewNavigation Index="3" Server="Server"/> <AdminServersViewNavigation Index="3" Server="Server"/>
<Allocations />
</Route> </Route>
<Route Path="/archive"> <Route Path="/archive">
<AdminServersViewNavigation Index="4" Server="Server"/> <AdminServersViewNavigation Index="4" Server="Server"/>
@@ -72,6 +73,7 @@
.Include(x => x.Allocations) .Include(x => x.Allocations)
.Include(x => x.MainAllocation) .Include(x => x.MainAllocation)
.Include(x => x.Variables) .Include(x => x.Variables)
.Include(x => x.Node)
.FirstOrDefault(x => x.Id == Id); .FirstOrDefault(x => x.Id == Id);
return Task.CompletedTask; return Task.CompletedTask;

View File

@@ -3,37 +3,18 @@
@using BlazorTable @using BlazorTable
@using Moonlight.App.Models.Misc @using Moonlight.App.Models.Misc
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.LogServices
@using Moonlight.Shared.Components.Navigations @using Moonlight.Shared.Components.Navigations
@inject LogService LogService
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
<OnlyAdmin> <OnlyAdmin>
<AdminSystemNavigation Index="1"/> <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> </OnlyAdmin>
@code @code
{ {
private LogEntry[] LogEntries;
private Task Load(LazyLoader arg) private Task Load(LazyLoader arg)
{ {
LogEntries = LogService.GetMessages();
return Task.CompletedTask; 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.Shared.Components.Navigations
@using Moonlight.App.Services.Sessions @using Moonlight.App.Services.Sessions
@using BlazorTable @using BlazorTable
@using Logging.Net
@using Moonlight.App.Helpers @using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc @using Moonlight.App.Models.Misc
@using Moonlight.App.Services @using Moonlight.App.Services

View File

@@ -5,6 +5,8 @@
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Mappy.Net @using Mappy.Net
@using Moonlight.App.Exceptions
@using Moonlight.App.Helpers
@inject UserRepository UserRepository @inject UserRepository UserRepository
@@ -13,7 +15,7 @@
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<SmartForm OnValidSubmit="Save" Model="Model"> <SmartForm OnValidSubmit="Save" Model="Model">
<div class="card mb-5 mb-xl-10"> <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="row">
<div class="col-lg-6 fv-row fv-plugins-icon-container"> <div class="col-lg-6 fv-row fv-plugins-icon-container">
<div class="mb-3"> <div class="mb-3">
@@ -74,7 +76,7 @@
@code @code
{ {
private UserDataModel Model = new UserDataModel(); private UserDataModel Model = new();
[CascadingParameter] [CascadingParameter]
public User CurrentUser { get; set; } public User CurrentUser { get; set; }
@@ -89,9 +91,20 @@
private Task Save() private Task Save()
{ {
CurrentUser = Mapper.Map(CurrentUser, Model); // Prevent users from locking out other users by changing their email
CurrentUser.Email = CurrentUser.Email.ToLower(); 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);
UserRepository.Update(CurrentUser); UserRepository.Update(CurrentUser);

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