53 Commits
v1b8 ... v1b12

Author SHA1 Message Date
Marcel Baumgartner
2fe17473ae Merge pull request #204 from Moonlight-Panel/SwitchToNewConfigSystem
Switched to new config system
2023-07-02 02:19:32 +02:00
Marcel Baumgartner
609cf8cfac Switched to new config system 2023-07-02 02:16:44 +02:00
Marcel Baumgartner
678da30b09 Merge pull request #203 from Moonlight-Panel/LogsAndFixes
Added new log files and log migrators. Fixed some errors with js invokes
2023-07-02 00:21:55 +02:00
Marcel Baumgartner
d19412f4bb Added new log files and log migrators. Fixed some errors with js invokes 2023-07-02 00:21:35 +02:00
Marcel Baumgartner
1665d6e537 Merge pull request #202 from Moonlight-Panel/AddSentrySupport
Added new sentry support
2023-07-01 19:01:00 +02:00
Marcel Baumgartner
fd210f2404 Added new sentry support 2023-07-01 19:00:38 +02:00
Marcel Baumgartner
c33729fb44 Merge pull request #201 from Moonlight-Panel/FixServerList
Fixed server list
2023-06-30 21:55:58 +02:00
Marcel Baumgartner
7983bf3ee4 Fixed server list 2023-06-30 21:55:32 +02:00
Marcel Baumgartner
a09f60aea7 Merge pull request #200 from Moonlight-Panel/HttpTimeoutFixes
Fixed timeout options from assumed seconds to real value miliseconds
2023-06-29 23:28:07 +02:00
Marcel Baumgartner
28b5893c21 Fixed timeout options from assumed seconds to real value miliseconds 2023-06-29 23:27:48 +02:00
Marcel Baumgartner
25b47d8b6c Merge pull request #199 from Moonlight-Panel/AddNewGermanTranslation
Added new german translations
2023-06-29 23:10:21 +02:00
Marcel Baumgartner
85f5b8a7da Added new german translations 2023-06-29 23:09:58 +02:00
Marcel Baumgartner
ab9333f99a Merge pull request #198 from Moonlight-Panel/FixServerDelete
Fixed server delete
2023-06-26 22:51:17 +02:00
Marcel Baumgartner
d60f8fc905 Fixed server delete 2023-06-26 22:50:45 +02:00
Marcel Baumgartner
fe1f4412d8 Merge pull request #197 from Moonlight-Panel/AddedRetry
Added retry class and added it to some api calls that need it
2023-06-26 22:45:56 +02:00
Marcel Baumgartner
f191533410 Added retry class and added it to some api calls that need it 2023-06-26 22:45:33 +02:00
Marcel Baumgartner
a894707536 Merge pull request #196 from Moonlight-Panel/SmallBugFixes
Small bug fixes
2023-06-26 18:05:58 +02:00
Marcel Baumgartner
d2ccc84286 Fixed sidebar text sizing issues 2023-06-26 17:23:25 +02:00
Marcel Baumgartner
f2ec43f2d2 Fixed not enabled installing screen for new servers 2023-06-26 17:19:54 +02:00
Marcel Baumgartner
7feccc8d9f Fixed error loop for fileaccess providers not supporting the launch url 2023-06-26 17:19:32 +02:00
Marcel Baumgartner
a8bd1193ce Merge pull request #195 from Moonlight-Panel/SecurityPatches
Security patches
2023-06-26 00:10:03 +02:00
Marcel Baumgartner
366d1a9205 Merge pull request #194 from Moonlight-Panel/AddStreamerModeAndFixUI
Added streamer mode and fixed security settings ui
2023-06-26 00:07:23 +02:00
Marcel Baumgartner
df9ed95c6b Added streamer mode and fixed security settings ui 2023-06-26 00:06:44 +02:00
Marcel Baumgartner
23a211362e Merge pull request #193 from Moonlight-Panel/EnhanceFileManager
Enhanced winscp button and new file/folder menu
2023-06-25 17:32:57 +02:00
Marcel Baumgartner
cf91d44902 Enhanced winscp button and new file/folder menu 2023-06-25 17:31:36 +02:00
Marcel Baumgartner
35633e21a9 Merge pull request #192 from Moonlight-Panel/EnhanceServerListLayout
Enhanced server list
2023-06-25 00:01:53 +02:00
Marcel Baumgartner
ce8b8f6798 Enhanced server list 2023-06-25 00:01:28 +02:00
Marcel Baumgartner
c28c80ba25 Merge pull request #191 from Moonlight-Panel/FixDnsManager
Fixed dns loading issues. Added udp support
2023-06-24 23:45:49 +02:00
Marcel Baumgartner
da17b1df93 Fixed dns loading issues. Added udp support 2023-06-24 23:45:29 +02:00
Marcel Baumgartner
f9f5865ef9 Prevent user locking when duplicating the email entries 2023-06-24 22:35:38 +02:00
Marcel Baumgartner
389ded9b77 Fixed oauth2 account spoofing using unverified discord accounts for claiming identity 2023-06-24 22:15:04 +02:00
Marcel Baumgartner
faebaa59dd Merge pull request #189 from Moonlight-Panel/AddCustomLayoutServerList
Added custom layout options for the server list
2023-06-24 02:35:21 +02:00
Marcel Baumgartner
6b7dc2ad05 Added custom layout options for the server list 2023-06-24 02:35:01 +02:00
Marcel Baumgartner
e356c9d0c8 Update README.md 2023-06-23 05:06:12 +02:00
Marcel Baumgartner
efed2a6a5c Merge pull request #188 from Moonlight-Panel/ImproveLogging
Improved logging. Added better error handling for mysql database backup
2023-06-23 00:51:32 +02:00
Marcel Baumgartner
6f138c2c51 Improved logging. Added better error handling for mysql database backup 2023-06-23 00:51:09 +02:00
Marcel Baumgartner
6c43e6a533 Merge pull request #187 from Moonlight-Panel/AddMalwareScan
Added maleware scan
2023-06-22 20:36:58 +02:00
Marcel Baumgartner
0379afd831 Added maleware scan 2023-06-22 20:36:33 +02:00
Marcel Baumgartner
b8bfdb7729 Merge pull request #185 from Moonlight-Panel/SwitchToSerilog
Switched to serilog as logging system
2023-06-21 19:15:53 +02:00
Marcel Baumgartner
72f60ec97c Switched to serilog as logging system 2023-06-21 19:15:30 +02:00
Marcel Baumgartner
1b40250750 Revert "Merge branch 'DiscordBot' into main"
This reverts commit cdf2988cb6, reversing
changes made to 76415b4a0a.
2023-06-20 20:59:49 +02:00
Ole Sziedat
cdf2988cb6 Merge branch 'DiscordBot' into main 2023-06-20 20:52:46 +02:00
Marcel Baumgartner
76415b4a0a Merge pull request #182 from Moonlight-Panel/ImproveFileEditor
Added STRG+S support for editor and better saving
2023-06-20 20:38:17 +02:00
Marcel Baumgartner
52d00baf2b Added STRG+S support for editor and better saving 2023-06-20 20:37:57 +02:00
Marcel Baumgartner
cd41db510e Merge pull request #181 from Moonlight-Panel/AddServerAllocationEditor
Added a basic allocation editor for servers
2023-06-20 19:21:41 +02:00
Marcel Baumgartner
1afd4e8b92 Added a basic allocation editor for servers 2023-06-20 19:18:18 +02:00
Marcel Baumgartner
ef37088c7a Merge pull request #180 from Moonlight-Panel/ImproveUserInterface
Fixed ui bugs, improved plugins page, added new 404 component
2023-06-20 02:56:15 +02:00
Marcel Baumgartner
e71495533b Fixed ui bugs, improved plugins page, added new 404 component 2023-06-20 02:55:50 +02:00
Marcel Baumgartner
e2a6d70f6a Update README.md 2023-06-18 06:23:17 +02:00
Marcel Baumgartner
e95853b09c Update README.md 2023-06-18 03:25:23 +02:00
Marcel Baumgartner
0537ca115e Remove assets from github language statistics 2023-06-18 02:50:08 +02:00
0fde9a5005 bot hotfix 2023-05-04 20:40:34 +02:00
Marcel Baumgartner
74541d7f87 Merge pull request #107 from Moonlight-Panel/main
Update to upstream branch
2023-04-29 23:39:46 +02:00
137 changed files with 5236 additions and 2427 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;
@@ -48,7 +47,7 @@ public class ModrinthApiHelper
var request = new RestRequest(url) var request = new RestRequest(url)
{ {
Timeout = 60 * 15 Timeout = 300000
}; };
request.AddHeader("Content-Type", "application/json"); request.AddHeader("Content-Type", "application/json");

View File

@@ -209,7 +209,7 @@ public class WingsApiHelper
var request = new RestRequest(url) var request = new RestRequest(url)
{ {
Timeout = 60 * 15 Timeout = 300000
}; };
request.AddHeader("Content-Type", "application/json"); request.AddHeader("Content-Type", "application/json");

View File

@@ -0,0 +1,226 @@
namespace Moonlight.App.Configuration;
using System;
using Newtonsoft.Json;
public class ConfigV1
{
[JsonProperty("Moonlight")] public MoonlightData Moonlight { get; set; } = new();
public class MoonlightData
{
[JsonProperty("AppUrl")] public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
[JsonProperty("DiscordBotApi")] public DiscordBotData DiscordBotApi { get; set; } = new();
[JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new();
[JsonProperty("Domains")] public DomainsData Domains { get; set; } = new();
[JsonProperty("Html")] public HtmlData Html { get; set; } = new();
[JsonProperty("Marketing")] public MarketingData Marketing { get; set; } = new();
[JsonProperty("OAuth2")] public OAuth2Data OAuth2 { get; set; } = new();
[JsonProperty("Security")] public SecurityData Security { get; set; } = new();
[JsonProperty("Mail")] public MailData Mail { get; set; } = new();
[JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
[JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new();
[JsonProperty("DiscordNotifications")]
public DiscordNotificationsData DiscordNotifications { get; set; } = new();
[JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
[JsonProperty("Rating")] public RatingData Rating { get; set; } = new();
[JsonProperty("SmartDeploy")] public SmartDeployData SmartDeploy { get; set; } = new();
[JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new();
}
public class CleanupData
{
[JsonProperty("Cpu")] public long Cpu { get; set; } = 90;
[JsonProperty("Memory")] public long Memory { get; set; } = 8192;
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
[JsonProperty("Uptime")] public long Uptime { get; set; } = 6;
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("MinUptime")] public long MinUptime { get; set; } = 10;
}
public class DatabaseData
{
[JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
[JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
[JsonProperty("Password")] public string Password { get; set; } = "secret";
[JsonProperty("Port")] public long Port { get; set; } = 3306;
[JsonProperty("Username")] public string Username { get; set; } = "moonlight_user";
}
public class DiscordBotData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("Token")] public string Token { get; set; } = "discord token here";
[JsonProperty("PowerActions")] public bool PowerActions { get; set; } = false;
[JsonProperty("SendCommands")] public bool SendCommands { get; set; } = false;
}
public class DiscordNotificationsData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("WebHook")] public string WebHook { get; set; } = "http://your-discord-webhook-url";
}
public class DomainsData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("AccountId")] public string AccountId { get; set; } = "cloudflare acc id";
[JsonProperty("Email")] public string Email { get; set; } = "cloudflare@acc.email";
[JsonProperty("Key")] public string Key { get; set; } = "secret";
}
public class HtmlData
{
[JsonProperty("Headers")] public HeadersData Headers { get; set; } = new();
}
public class HeadersData
{
[JsonProperty("Color")] public string Color { get; set; } = "#4b27e8";
[JsonProperty("Description")] public string Description { get; set; } = "the next generation hosting panel";
[JsonProperty("Keywords")] public string Keywords { get; set; } = "moonlight";
[JsonProperty("Title")] public string Title { get; set; } = "Moonlight - endelon.link";
}
public class MailData
{
[JsonProperty("Email")] public string Email { get; set; } = "username@your.mail.host";
[JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
[JsonProperty("Password")] public string Password { get; set; } = "secret";
[JsonProperty("Port")] public int Port { get; set; } = 465;
[JsonProperty("Ssl")] public bool Ssl { get; set; } = true;
}
public class MarketingData
{
[JsonProperty("BrandName")] public string BrandName { get; set; } = "Endelon Hosting";
[JsonProperty("Imprint")] public string Imprint { get; set; } = "https://your-site.xyz/imprint";
[JsonProperty("Privacy")] public string Privacy { get; set; } = "https://your-site.xyz/privacy";
[JsonProperty("About")] public string About { get; set; } = "https://your-site.xyz/about";
[JsonProperty("Website")] public string Website { get; set; } = "https://your-site.xyz";
}
public class OAuth2Data
{
[JsonProperty("OverrideUrl")] public string OverrideUrl { get; set; } = "https://only-for-development.cases";
[JsonProperty("EnableOverrideUrl")] public bool EnableOverrideUrl { get; set; } = false;
[JsonProperty("Providers")]
public OAuth2ProviderData[] Providers { get; set; } = Array.Empty<OAuth2ProviderData>();
}
public class OAuth2ProviderData
{
[JsonProperty("Id")] public string Id { get; set; }
[JsonProperty("ClientId")] public string ClientId { get; set; }
[JsonProperty("ClientSecret")] public string ClientSecret { get; set; }
}
public class RatingData
{
[JsonProperty("Enabled")] public bool Enabled { get; set; } = false;
[JsonProperty("Url")] public string Url { get; set; } = "https://link-to-google-or-smth";
[JsonProperty("MinRating")] public int MinRating { get; set; } = 4;
[JsonProperty("DaysSince")] public int DaysSince { get; set; } = 5;
}
public class SecurityData
{
[JsonProperty("Token")] public string Token { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; }
}
public class ReCaptchaData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("SiteKey")] public string SiteKey { get; set; } = "recaptcha site key here";
[JsonProperty("SecretKey")] public string SecretKey { get; set; } = "recaptcha secret here";
}
public class SentryData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("Dsn")] public string Dsn { get; set; } = "http://your-sentry-url-here";
}
public class SmartDeployData
{
[JsonProperty("Server")] public SmartDeployServerData Server { get; set; } = new();
}
public class SmartDeployServerData
{
[JsonProperty("EnableOverride")] public bool EnableOverride { get; set; } = false;
[JsonProperty("OverrideNode")] public long OverrideNode { get; set; } = 1;
}
public class StatisticsData
{
[JsonProperty("Enabled")] public bool Enabled { get; set; } = false;
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
}
public class SubscriptionsData
{
[JsonProperty("SellPass")] public SellPassData SellPass { get; set; } = new();
}
public class SellPassData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[JsonProperty("Url")] public string Url { get; set; } = "https://not-implemented-yet";
}
}

View File

@@ -52,14 +52,14 @@ public class DataContext : DbContext
if (!optionsBuilder.IsConfigured) if (!optionsBuilder.IsConfigured)
{ {
var config = ConfigService var config = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Database"); .Moonlight.Database;
var connectionString = $"host={config.GetValue<string>("Host")};" + var connectionString = $"host={config.Host};" +
$"port={config.GetValue<int>("Port")};" + $"port={config.Port};" +
$"database={config.GetValue<string>("Database")};" + $"database={config.Database};" +
$"uid={config.GetValue<string>("Username")};" + $"uid={config.Username};" +
$"pwd={config.GetValue<string>("Password")}"; $"pwd={config.Password}";
optionsBuilder.UseMySql( optionsBuilder.UseMySql(
connectionString, connectionString,

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

@@ -0,0 +1,30 @@
using Microsoft.JSInterop;
namespace Moonlight.App.Extensions;
public static class JSRuntimeExtensions
{
public static async Task InvokeVoidSafeAsync(this IJSRuntime jsRuntime, string method, params object[] args)
{
try
{
await jsRuntime.InvokeVoidAsync(method, args);
}
catch (Exception)
{
// ignored
}
}
public static void InvokeVoidSafe(this IJSRuntime jsRuntime, string method, params object[] args)
{
try
{
jsRuntime.InvokeVoidAsync(method, args);
}
catch (Exception)
{
// ignored
}
}
}

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;
@@ -67,21 +66,21 @@ public class DatabaseCheckupService
var configService = new ConfigService(new StorageService()); var configService = new ConfigService(new StorageService());
var dateTimeService = new DateTimeService(); var dateTimeService = new DateTimeService();
var config = configService var config = configService.Get().Moonlight.Database;
.GetSection("Moonlight")
.GetSection("Database");
var connectionString = $"host={config.GetValue<string>("Host")};" + var connectionString = $"host={config.Host};" +
$"port={config.GetValue<int>("Port")};" + $"port={config.Port};" +
$"database={config.GetValue<string>("Database")};" + $"database={config.Database};" +
$"uid={config.GetValue<string>("Username")};" + $"uid={config.Username};" +
$"pwd={config.GetValue<string>("Password")}"; $"pwd={config.Password}";
string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql"); string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql");
Logger.Info($"Saving it to: {file}"); Logger.Info($"Saving it to: {file}");
Logger.Info("Starting backup..."); Logger.Info("Starting backup...");
try
{
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
@@ -97,4 +96,15 @@ public class DatabaseCheckupService
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

@@ -111,7 +111,7 @@ public class WingsFileAccess : FileAccess
request.AddParameter("name", "files"); request.AddParameter("name", "files");
request.AddParameter("filename", name); request.AddParameter("filename", name);
request.AddHeader("Content-Type", "multipart/form-data"); request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("Origin", ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")); request.AddHeader("Origin", ConfigService.Get().Moonlight.AppUrl);
request.AddFile("files", () => request.AddFile("files", () =>
{ {
return new StreamProgressHelper(dataStream) return new StreamProgressHelper(dataStream)

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

@@ -0,0 +1,66 @@
namespace Moonlight.App.Helpers;
public class Retry
{
private List<Type> RetryExceptionTypes;
private List<Func<Exception, bool>> RetryFilters;
private int RetryTimes = 1;
public Retry()
{
RetryExceptionTypes = new();
RetryFilters = new();
}
public Retry Times(int times)
{
RetryTimes = times;
return this;
}
public Retry At(Func<Exception, bool> filter)
{
RetryFilters.Add(filter);
return this;
}
public Retry At<T>()
{
RetryExceptionTypes.Add(typeof(T));
return this;
}
public async Task Call(Func<Task> method)
{
int triesLeft = RetryTimes;
do
{
try
{
await method.Invoke();
return;
}
catch (Exception e)
{
if(triesLeft < 1) // Throw if no tries left
throw;
if (RetryExceptionTypes.Any(x => x.FullName == e.GetType().FullName))
{
triesLeft--;
continue;
}
if (RetryFilters.Any(x => x.Invoke(e)))
{
triesLeft--;
continue;
}
// Throw if not filtered -> unknown/unhandled
throw;
}
} while (triesLeft >= 0);
}
}

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

@@ -20,7 +20,7 @@ public class WingsConsoleHelper
{ {
ServerRepository = serverRepository; ServerRepository = serverRepository;
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = configService.Get().Moonlight.AppUrl;
} }
public async Task ConnectWings(WingsConsole console, Server server) public async Task ConnectWings(WingsConsole console, Server server)

View File

@@ -15,7 +15,7 @@ public class WingsJwtHelper
{ {
ConfigService = configService; ConfigService = configService;
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = ConfigService.Get().Moonlight.AppUrl;
} }
public string Generate(string secret, Action<Dictionary<string, string>> claimsAction) public string Generate(string secret, Action<Dictionary<string, string>> claimsAction)

View File

@@ -30,14 +30,14 @@ public class DiscordBotController : Controller
ServerService = serverService; ServerService = serverService;
var config = configService var config = configService
.GetSection("Moonlight") .Get()
.GetSection("DiscordBotApi"); .Moonlight.DiscordBotApi;
Enable = config.GetValue<bool>("Enable"); Enable = config.Enable;
if (Enable) if (Enable)
{ {
Token = config.GetValue<string>("Token"); Token = config.Token;
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text; using System.Text;
using Logging.Net;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
@@ -9,7 +8,6 @@ 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;

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;
@@ -28,19 +27,39 @@ public class LogMigrator : ILogger
switch (logLevel) switch (logLevel)
{ {
case LogLevel.Critical: case LogLevel.Critical:
Logger.Fatal($"[{Name}] {formatter(state, exception)}"); Logger.Fatal(formatter(state, exception));
if(exception != null)
Logger.Fatal(exception);
break; break;
case LogLevel.Warning: case LogLevel.Warning:
Logger.Warn($"[{Name}] {formatter(state, exception)}"); Logger.Warn(formatter(state, exception));
if(exception != null)
Logger.Warn(exception);
break; break;
case LogLevel.Debug: case LogLevel.Debug:
Logger.Debug($"[{Name}] {formatter(state, exception)}"); Logger.Debug(formatter(state, exception));
if(exception != null)
Logger.Debug(exception);
break; break;
case LogLevel.Error: case LogLevel.Error:
Logger.Error($"[{Name}] {formatter(state, exception)}"); Logger.Error(formatter(state, exception));
if(exception != null)
Logger.Error(exception);
break; break;
case LogLevel.Information: case LogLevel.Information:
Logger.Info($"[{Name}] {formatter(state, exception)}"); Logger.Info(formatter(state, exception));
if(exception != null)
Logger.Info(exception);
break; break;
} }
} }

View File

@@ -0,0 +1,71 @@
using Moonlight.App.Helpers;
using Sentry;
using Sentry.Extensibility;
namespace Moonlight.App.LogMigrator;
public class SentryDiagnosticsLogger : IDiagnosticLogger
{
private readonly SentryLevel Level;
public SentryDiagnosticsLogger(SentryLevel level)
{
Level = level;
}
public bool IsEnabled(SentryLevel level)
{
if ((int)level >= (int)Level)
{
return true;
}
return false;
}
public void Log(SentryLevel logLevel, string message, Exception? exception = null, params object?[] args)
{
switch (logLevel)
{
case SentryLevel.Debug:
Logger.Debug(string.Format(message, args));
if(exception != null)
Logger.Debug(exception);
break;
case SentryLevel.Info:
Logger.Info(string.Format(message, args));
if(exception != null)
Logger.Info(exception);
break;
case SentryLevel.Warning:
Logger.Warn(string.Format(message, args));
if(exception != null)
Logger.Warn(exception);
break;
case SentryLevel.Error:
Logger.Error(string.Format(message, args));
if(exception != null)
Logger.Error(exception);
break;
case SentryLevel.Fatal:
Logger.Fatal(string.Format(message, args));
if(exception != null)
Logger.Fatal(exception);
break;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System.Text; using 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;
@@ -44,15 +43,15 @@ public class CleanupService
CompletedAt = DateTimeService.GetCurrent(); CompletedAt = DateTimeService.GetCurrent();
IsRunning = false; IsRunning = false;
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); var config = ConfigService.Get().Moonlight.Cleanup;
if (!config.GetValue<bool>("Enable") || ConfigService.DebugMode) if (!config.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.Wait));
Task.Run(Run); Task.Run(Run);
} }
@@ -64,12 +63,12 @@ public class CleanupService
IsRunning = true; IsRunning = true;
using var scope = ServiceScopeFactory.CreateScope(); using var scope = ServiceScopeFactory.CreateScope();
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); var config = ConfigService.Get().Moonlight.Cleanup;
var maxCpu = config.GetValue<int>("Cpu"); var maxCpu = config.Cpu;
var minMemory = config.GetValue<int>("Memory"); var minMemory = config.Memory;
var maxUptime = config.GetValue<int>("Uptime"); var maxUptime = config.Uptime;
var minUptime = config.GetValue<int>("MinUptime"); var minUptime = config.MinUptime;
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>(); var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>(); var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();

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;
@@ -22,14 +22,14 @@ public class DiscordNotificationService
Event = eventSystem; Event = eventSystem;
ResourceService = resourceService; ResourceService = resourceService;
var config = configService.GetSection("Moonlight").GetSection("DiscordNotifications"); var config = configService.Get().Moonlight.DiscordNotifications;
if (config.GetValue<bool>("Enable")) if (config.Enable)
{ {
Logger.Info("Discord notifications enabled"); Logger.Info("Discord notifications enabled");
Client = new(config.GetValue<string>("WebHook")); Client = new(config.WebHook);
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = configService.Get().Moonlight.AppUrl;
Event.On<User>("supportChat.new", this, OnNewSupportChat); Event.On<User>("supportChat.new", this, OnNewSupportChat);
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage); Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);

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,16 +1,14 @@
using System.Text; using Moonlight.App.Configuration;
using Logging.Net;
using Microsoft.Extensions.Primitives;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Services.Files; using Moonlight.App.Services.Files;
using Newtonsoft.Json;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
public class ConfigService : IConfiguration public class ConfigService
{ {
private readonly StorageService StorageService; private readonly StorageService StorageService;
private ConfigV1 Configuration;
private IConfiguration Configuration;
public bool DebugMode { get; private set; } = false; public bool DebugMode { get; private set; } = false;
public bool SqlDebugMode { get; private set; } = false; public bool SqlDebugMode { get; private set; } = false;
@@ -42,37 +40,22 @@ public class ConfigService : IConfiguration
public void Reload() public void Reload()
{ {
Logger.Info($"Reading config from '{PathBuilder.File("storage", "configs", "config.json")}'"); var path = PathBuilder.File("storage", "configs", "config.json");
Configuration = new ConfigurationBuilder().AddJsonStream( if (!File.Exists(path))
new MemoryStream(Encoding.ASCII.GetBytes(
File.ReadAllText(
PathBuilder.File("storage", "configs", "config.json")
)
)
)).Build();
Logger.Info("Reloaded configuration file");
}
public IEnumerable<IConfigurationSection> GetChildren()
{ {
return Configuration.GetChildren(); File.WriteAllText(path, "{}");
} }
public IChangeToken GetReloadToken() Configuration = JsonConvert.DeserializeObject<ConfigV1>(
{ File.ReadAllText(path)
return Configuration.GetReloadToken(); ) ?? new ConfigV1();
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration));
} }
public IConfigurationSection GetSection(string key) public ConfigV1 Get()
{ {
return Configuration.GetSection(key); return Configuration;
}
public string this[string key]
{
get => Configuration[key];
set => Configuration[key] = value;
} }
} }

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;
@@ -30,7 +29,7 @@ public class ServerListCommand : BaseModule
{ {
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, command.User); embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, command.User);
components = new ComponentBuilder(); components = new ComponentBuilder();
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.GetSection("Moonlight").GetValue<String>("AppUrl")); components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.Get().Moonlight.AppUrl);
await command.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true); await command.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
return; return;
@@ -58,7 +57,7 @@ public class ServerListCommand : BaseModule
components.WithButton("Panel", components.WithButton("Panel",
emote: Emote.Parse("<a:Earth:1092814004113657927>"), emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link, style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}"); url: $"{ConfigService.Get().Moonlight.AppUrl}");
if (servers.Count > 25) if (servers.Count > 25)
{ {

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;
@@ -45,10 +44,10 @@ public DiscordBotService(
ServiceScope = ServiceScopeFactory.CreateScope(); ServiceScope = ServiceScopeFactory.CreateScope();
var discordConfig = ConfigService var discordConfig = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("DiscordBot"); .Moonlight.DiscordBot;
if (!discordConfig.GetValue<bool>("Enable")) if (!discordConfig.Enable)
return; return;
Client.Log += Log; Client.Log += Log;
@@ -68,7 +67,7 @@ public DiscordBotService(
await ActivityStatusModule.UpdateActivityStatusList(); await ActivityStatusModule.UpdateActivityStatusList();
await Client.LoginAsync(TokenType.Bot, discordConfig.GetValue<string>("Token")); await Client.LoginAsync(TokenType.Bot, discordConfig.Token);
await Client.StartAsync(); await Client.StartAsync();
await Task.Delay(-1); await Task.Delay(-1);

View File

@@ -87,8 +87,8 @@ public class EmbedBuilderModule : BaseModule
int[] randomNumbers = new int[] { 1, 3, 8, 11, 20 }; int[] randomNumbers = new int[] { 1, 3, 8, 11, 20 };
if (randomNumbers.Contains(random.Next(1, 24))) if (randomNumbers.Contains(random.Next(1, 24)))
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username + " - The Rick version").WithUrl(ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")).WithIconUrl("https://cdn.discordapp.com/attachments/750696464014901268/1092783310129860618/rick.gif"); return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username + " - The Rick version").WithUrl(ConfigService.Get().Moonlight.AppUrl).WithIconUrl("https://cdn.discordapp.com/attachments/750696464014901268/1092783310129860618/rick.gif");
return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username).WithUrl(ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")).WithIconUrl(Client.CurrentUser.GetAvatarUrl()); return new EmbedAuthorBuilder().WithName(Client.CurrentUser.Username).WithUrl(ConfigService.Get().Moonlight.AppUrl).WithIconUrl(Client.CurrentUser.GetAvatarUrl());
} }
} }

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;
@@ -72,7 +72,7 @@ public class ServerListComponentHandlerModule : BaseModule
// stopping // stopping
// offline // offline
// installing // installing
if (!ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("PowerActions") && costomId[1] is "Start" or "Restart" or "Stop" or "Kill" or "Update") if (!ConfigService.Get().Moonlight.DiscordBot.PowerActions && costomId[1] is "Start" or "Restart" or "Stop" or "Kill" or "Update")
{ {
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User); embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
await component.RespondAsync(embed: embed.Build(), ephemeral: true); await component.RespondAsync(embed: embed.Build(), ephemeral: true);
@@ -80,7 +80,7 @@ public class ServerListComponentHandlerModule : BaseModule
return; return;
} }
if (!ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("SendCommands") && costomId[1] is "SendCommand") if (!ConfigService.Get().Moonlight.DiscordBot.SendCommands && costomId[1] is "SendCommand")
{ {
embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User); embed = dcs.EmbedBuilderModule.StandardEmbed($"This feature is disabled for Security reasons! \n If you believe this is a error please contact the Administrators from this panel.", Color.Red, component.User);
await component.RespondAsync(embed: embed.Build(), ephemeral: true); await component.RespondAsync(embed: embed.Build(), ephemeral: true);
@@ -302,7 +302,7 @@ public class ServerListComponentHandlerModule : BaseModule
{ {
embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, component.User); embed = dcs.EmbedBuilderModule.StandardEmbed("Sorry ;( \n Please first create and/or link a Account to Discord! \n Press the Button to register/log in.", Color.Red, component.User);
components = new ComponentBuilder(); components = new ComponentBuilder();
components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.GetSection("Moonlight").GetValue<String>("AppUrl")); components.WithButton("Click Here", style: ButtonStyle.Link, url: ConfigService.Get().Moonlight.AppUrl);
await component.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true); await component.RespondAsync(embed: embed.Build(), components: components.Build(), ephemeral: true);
return; return;
@@ -332,7 +332,7 @@ public class ServerListComponentHandlerModule : BaseModule
components.WithButton("Panel", components.WithButton("Panel",
emote: Emote.Parse("<a:Earth:1092814004113657927>"), emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link, style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}"); url: $"{ConfigService.Get().Moonlight.AppUrl}");
components.WithButton("Previous-page", components.WithButton("Previous-page",
emote: Emote.Parse("<:ArrowLeft:1101547474180649030>"), emote: Emote.Parse("<:ArrowLeft:1101547474180649030>"),
@@ -378,7 +378,7 @@ public class ServerListComponentHandlerModule : BaseModule
var components = new ComponentBuilder(); var components = new ComponentBuilder();
if (ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("PowerActions")) if (ConfigService.Get().Moonlight.DiscordBot.PowerActions)
{ {
components.WithButton("Start", style: ButtonStyle.Success, customId: $"Sm.Start.{server.Id}", disabled: false); components.WithButton("Start", style: ButtonStyle.Success, customId: $"Sm.Start.{server.Id}", disabled: false);
components.WithButton("Restart", style: ButtonStyle.Primary, customId: $"Sm.Restart.{server.Id}", disabled: false); components.WithButton("Restart", style: ButtonStyle.Primary, customId: $"Sm.Restart.{server.Id}", disabled: false);
@@ -389,14 +389,14 @@ public class ServerListComponentHandlerModule : BaseModule
components.WithButton("Way2Server", components.WithButton("Way2Server",
emote: Emote.Parse("<a:Earth:1092814004113657927>"), emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link, style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}/server/{server.Uuid}"); url: $"{ConfigService.Get().Moonlight.AppUrl}/server/{server.Uuid}");
components.WithButton("Update", components.WithButton("Update",
emote: Emote.Parse("<:refresh:1101547898803605605>"), emote: Emote.Parse("<:refresh:1101547898803605605>"),
style: ButtonStyle.Secondary, style: ButtonStyle.Secondary,
customId: $"Sm.Update.{server.Id}"); customId: $"Sm.Update.{server.Id}");
if (ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("SendCommands")) if (ConfigService.Get().Moonlight.DiscordBot.SendCommands)
{ {
components.WithButton("SendCommand", components.WithButton("SendCommand",
emote: Emote.Parse("<:Console:1101547358157819944>"), emote: Emote.Parse("<:Console:1101547358157819944>"),

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,29 +22,26 @@ 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") .Get()
.GetSection("Domains"); .Moonlight.Domains;
AccountId = config.GetValue<string>("AccountId"); AccountId = config.AccountId;
Client = new( Client = new(
new ApiKeyAuthentication( new ApiKeyAuthentication(
config.GetValue<string>("Email"), config.Email,
config.GetValue<string>("Key") config.Key
) )
); );
} }
@@ -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

@@ -8,7 +8,7 @@ public class ResourceService
public ResourceService(ConfigService configService) public ResourceService(ConfigService configService)
{ {
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = configService.Get().Moonlight.AppUrl;
} }
public string Image(string name) public string Image(string name)

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;
@@ -16,6 +15,7 @@ public class StorageService
Directory.CreateDirectory(PathBuilder.Dir("storage", "configs")); Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "resources")); Directory.CreateDirectory(PathBuilder.Dir("storage", "resources"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups")); Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
if(IsEmpty(PathBuilder.Dir("storage", "resources"))) if(IsEmpty(PathBuilder.Dir("storage", "resources")))
{ {

View File

@@ -25,16 +25,15 @@ public class ReCaptchaService
ConfigService = configService; ConfigService = configService;
var recaptchaConfig = ConfigService var recaptchaConfig = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Security") .Moonlight.Security.ReCaptcha;
.GetSection("ReCaptcha");
Enable = recaptchaConfig.GetValue<bool>("Enable"); Enable = recaptchaConfig.Enable;
if (Enable) if (Enable)
{ {
SiteKey = recaptchaConfig.GetValue<string>("SiteKey"); SiteKey = recaptchaConfig.SiteKey;
SecretKey = recaptchaConfig.GetValue<string>("SecretKey"); SecretKey = recaptchaConfig.SecretKey;
} }
} }

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;
@@ -18,14 +17,14 @@ public class MailService
public MailService(ConfigService configService) public MailService(ConfigService configService)
{ {
var mailConfig = configService var mailConfig = configService
.GetSection("Moonlight") .Get()
.GetSection("Mail"); .Moonlight.Mail;
Server = mailConfig.GetValue<string>("Server"); Server = mailConfig.Server;
Password = mailConfig.GetValue<string>("Password"); Password = mailConfig.Password;
Email = mailConfig.GetValue<string>("Email"); Email = mailConfig.Email;
Port = mailConfig.GetValue<int>("Port"); Port = mailConfig.Port;
Ssl = mailConfig.GetValue<bool>("Ssl"); Ssl = mailConfig.Ssl;
} }
public async Task SendMail( public async Task SendMail(

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;

View File

@@ -1,4 +1,5 @@
using Moonlight.App.Database.Entities; using Mappy.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
@@ -24,14 +25,17 @@ public class OAuth2Service
ConfigService = configService; ConfigService = configService;
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
var config = ConfigService.GetSection("Moonlight").GetSection("OAuth2"); var config = ConfigService
.Get()
.Moonlight.OAuth2;
Configs = config.GetSection("Providers").Get<OAuth2ProviderConfig[]>() Configs = config.Providers
?? Array.Empty<OAuth2ProviderConfig>(); .Select(Mapper.Map<OAuth2ProviderConfig>)
.ToArray();
OverrideUrl = config.GetValue<string>("OverrideUrl"); OverrideUrl = config.OverrideUrl;
EnableOverrideUrl = config.GetValue<bool>("EnableOverrideUrl"); EnableOverrideUrl = config.EnableOverrideUrl;
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = configService.Get().Moonlight.AppUrl;
// Register additional providers here // Register additional providers here
RegisterOAuth2<DiscordOAuth2Provider>("discord"); RegisterOAuth2<DiscordOAuth2Provider>("discord");

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)
@@ -30,10 +25,9 @@ public class OneTimeJwtService
var opt = new Dictionary<string, string>(); var opt = new Dictionary<string, string>();
options.Invoke(opt); options.Invoke(opt);
string secret = ConfigService var secret = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Security") .Moonlight.Security.Token;
.GetValue<string>("Token");
var id = StringHelper.GenerateString(16); var id = StringHelper.GenerateString(16);
@@ -60,10 +54,9 @@ public class OneTimeJwtService
public async Task<Dictionary<string, string>?> Validate(string token) public async Task<Dictionary<string, string>?> Validate(string token)
{ {
string secret = ConfigService var secret = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Security") .Moonlight.Security.Token;
.GetValue<string>("Token");
string json; string json;
@@ -76,10 +69,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

@@ -26,12 +26,12 @@ public class RatingService
Event = eventSystem; Event = eventSystem;
UserRepository = userRepository; UserRepository = userRepository;
var config = configService.GetSection("Moonlight").GetSection("Rating"); var config = configService.Get().Moonlight.Rating;
Enabled = config.GetValue<bool>("Enabled"); Enabled = config.Enabled;
Url = config.GetValue<string>("Url"); Url = config.Url;
MinRating = config.GetValue<int>("MinRating"); MinRating = config.MinRating;
DaysSince = config.GetValue<int>("DaysSince"); DaysSince = config.DaysSince;
} }
public async Task<bool> ShouldRate() public async Task<bool> ShouldRate()

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;
@@ -90,10 +79,20 @@ public class ServerService
{ {
Server server = EnsureNodeData(s); Server server = EnsureNodeData(s);
return await WingsApiHelper.Get<ServerDetails>( ServerDetails result = null!;
await new Retry()
.Times(3)
.At(x => x.Message.Contains("A task was canceled"))
.Call(async () =>
{
result = await WingsApiHelper.Get<ServerDetails>(
server.Node, server.Node,
$"api/servers/{server.Uuid}" $"api/servers/{server.Uuid}"
); );
});
return result;
} }
public async Task SetPowerState(Server s, PowerSignal signal) public async Task SetPowerState(Server s, PowerSignal signal)
@@ -107,11 +106,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)
@@ -124,7 +119,8 @@ public class ServerService
var backup = new ServerBackup() var backup = new ServerBackup()
{ {
Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}", Name =
$"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
Uuid = Guid.NewGuid(), Uuid = Guid.NewGuid(),
CreatedAt = DateTimeService.GetCurrent(), CreatedAt = DateTimeService.GetCurrent(),
Created = false Created = false
@@ -140,12 +136,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 +173,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)
@@ -200,8 +186,15 @@ public class ServerService
try try
{ {
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}", await new Retry()
.Times(3)
.At(x => x.Message.Contains("A task was canceled"))
.Call(async () =>
{
await WingsApiHelper.Delete(serverData.Node,
$"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
null); null);
});
} }
catch (WingsException e) catch (WingsException e)
{ {
@@ -220,13 +213,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 +226,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}";
@@ -293,7 +275,8 @@ public class ServerService
freeAllocations = NodeAllocationRepository freeAllocations = NodeAllocationRepository
.Get() .Get()
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}") .FromSqlRaw(
$"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
.ToArray(); .ToArray();
} }
catch (Exception) catch (Exception)
@@ -321,7 +304,8 @@ public class ServerService
Allocations = freeAllocations.ToList(), Allocations = freeAllocations.ToList(),
Backups = new(), Backups = new(),
OverrideStartup = "", OverrideStartup = "",
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default) DockerImageIndex = image.DockerImages.FindIndex(x => x.Default),
Installing = true
}; };
foreach (var imageVariable in image.Variables) foreach (var imageVariable in image.Variables)
@@ -339,24 +323,27 @@ public class ServerService
var newServerData = ServerRepository.Add(server); var newServerData = ServerRepository.Add(server);
try try
{
await new Retry()
.Times(3)
.At(x => x.Message.Contains("A task was canceled"))
.Call(async () =>
{ {
await WingsApiHelper.Post(node, $"api/servers", new CreateServer() await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
{ {
Uuid = newServerData.Uuid, Uuid = newServerData.Uuid,
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 +360,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 +369,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");
} }
@@ -408,8 +395,6 @@ public class ServerService
public async Task Delete(Server s) public async Task Delete(Server s)
{ {
throw new DisplayException("Deleting servers is currently disabled");
var backups = await GetBackups(s); var backups = await GetBackups(s);
foreach (var backup in backups) foreach (var backup in backups)
@@ -430,7 +415,21 @@ public class ServerService
.Include(x => x.Node) .Include(x => x.Node)
.First(x => x.Id == s.Id); .First(x => x.Id == s.Id);
try
{
await new Retry()
.Times(3)
.At(x => x.Message.Contains("A task was canceled"))
.Call(async () =>
{
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null); await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
});
}
catch (WingsException e)
{
if (e.StatusCode != 404)
throw;
}
foreach (var variable in server.Variables.ToArray()) foreach (var variable in server.Variables.ToArray())
{ {

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,20 +23,15 @@ 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") .Get()
.GetSection("Security") .Moonlight.Security.Token;
.GetValue<string>("Token");
} }
public async Task<User?> Get() public async Task<User?> Get()
@@ -90,15 +81,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 +123,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;
} }
} }

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

@@ -28,13 +28,12 @@ public class SmartDeployService
public async Task<Node?> GetNode() public async Task<Node?> GetNode()
{ {
var config = ConfigService var config = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("SmartDeploy") .Moonlight.SmartDeploy.Server;
.GetSection("Server");
if (config.GetValue<bool>("EnableOverride")) if (config.EnableOverride)
{ {
var nodeId = config.GetValue<int>("OverrideNode"); var nodeId = config.OverrideNode;
return NodeRepository.Get().FirstOrDefault(x => x.Id == nodeId); return NodeRepository.Get().FirstOrDefault(x => x.Id == nodeId);
} }

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;
@@ -18,13 +17,13 @@ public class StatisticsCaptureService
DateTimeService = dateTimeService; DateTimeService = dateTimeService;
var config = configService var config = configService
.GetSection("Moonlight") .Get()
.GetSection("Statistics"); .Moonlight.Statistics;
if(!config.GetValue<bool>("Enabled")) if(!config.Enabled)
return; return;
var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait")); var period = TimeSpan.FromMinutes(config.Wait);
Timer = new(period); Timer = new(period);
Logger.Info("Starting statistics system"); Logger.Info("Starting statistics system");
@@ -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,17 +32,14 @@ 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;
DateTimeService = dateTimeService; DateTimeService = dateTimeService;
JwtSecret = configService JwtSecret = configService
.GetSection("Moonlight") .Get()
.GetSection("Security") .Moonlight.Security.Token;
.GetValue<string>("Token");
} }
public async Task<string> Register(string email, string password, string firstname, string lastname) public async Task<string> Register(string email, string password, string firstname, string lastname)
@@ -85,10 +77,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 +91,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 +100,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 +125,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 +149,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 +162,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 +172,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 +226,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,6 +1,4 @@
using System.Net; using DnsClient;
using DnsClient;
using Logging.Net;
using Microsoft.EntityFrameworkCore; 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;

View File

@@ -26,7 +26,6 @@
<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" />
@@ -47,6 +46,12 @@
<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="Sentry.AspNetCore" Version="3.33.1" />
<PackageReference Include="Sentry.Serilog" Version="3.33.1" />
<PackageReference Include="Serilog" Version="3.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.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" />
@@ -98,4 +103,12 @@
<AdditionalFiles Include="Shared\Views\Server\Settings\ServerResetSetting.razor" /> <AdditionalFiles Include="Shared\Views\Server\Settings\ServerResetSetting.razor" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="storage\configs\config.json.bak">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project> </Project>

View File

@@ -11,12 +11,12 @@
@{ @{
var headerConfig = ConfigService var headerConfig = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Html") .Moonlight.Html.Headers;
.GetSection("Headers");
var moonlightConfig = ConfigService var moonlightConfig = ConfigService
.GetSection("Moonlight"); .Get()
.Moonlight;
} }
<!DOCTYPE html> <!DOCTYPE html>
@@ -26,16 +26,16 @@
<meta property="og:locale" content="de_DE"/> <meta property="og:locale" content="de_DE"/>
<meta property="og:type" content="article"/> <meta property="og:type" content="article"/>
<meta content="@(headerConfig.GetValue<string>("Title"))" property="og:title"/> <meta content="@(headerConfig.Title)" property="og:title"/>
<meta content="@(headerConfig.GetValue<string>("Description"))" property="og:description"/> <meta content="@(headerConfig.Description)" property="og:description"/>
<meta content="@(moonlightConfig.GetValue<string>("AppUrl"))" property="og:url"/> <meta content="@(moonlightConfig.AppUrl)" property="og:url"/>
<meta content="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" property="og:image"/> <meta content="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logolong.png" property="og:image"/>
<meta content="@(headerConfig.GetValue<string>("Color"))" data-react-helmet="true" name="theme-color"/> <meta content="@(headerConfig.Color)" data-react-helmet="true" name="theme-color"/>
<meta content="@(headerConfig.GetValue<string>("Description"))" name="description"/> <meta content="@(headerConfig.Description)" name="description"/>
<meta content="@(headerConfig.GetValue<string>("Keywords"))" name="keywords"/> <meta content="@(headerConfig.Keywords)" name="keywords"/>
<link rel="shortcut icon" href="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg"/> <link rel="shortcut icon" href="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logo.svg"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"/> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"/>
@@ -75,7 +75,7 @@
<div id="flashbang" class="flashbanglight"></div> <div id="flashbang" class="flashbanglight"></div>
<div class="app-page-loader flex-column"> <div class="app-page-loader flex-column">
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/> <img alt="Logo" src="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logo.svg" class="h-25px"/>
@{ @{
string loadingMessage; string loadingMessage;
@@ -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,16 @@ 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 Sentry;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
namespace Moonlight namespace Moonlight
{ {
@@ -38,14 +40,72 @@ 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());
var shouldUseSentry = configService
.Get()
.Moonlight.Sentry.Enable;
if (configService.DebugMode)
{
if (shouldUseSentry)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
.WriteTo.Sentry(options =>
{
options.MinimumBreadcrumbLevel = LogEventLevel.Debug;
options.MinimumEventLevel = LogEventLevel.Warning;
})
.CreateLogger();
}
else
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
.CreateLogger();
}
}
else
{
if (shouldUseSentry)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.WriteTo.Sentry(options =>
{
options.MinimumBreadcrumbLevel = LogEventLevel.Information;
options.MinimumEventLevel = LogEventLevel.Warning;
})
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
.CreateLogger();
}
else
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
.WriteTo.File(PathBuilder.File("storage", "logs", $"{DateTime.UtcNow:yyyy-MM-dd}.log"))
.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();
@@ -54,8 +114,8 @@ namespace Moonlight
// Switch to logging.net injection // Switch to logging.net injection
// TODO: Enable in production // TODO: Enable in production
//builder.Logging.ClearProviders(); builder.Logging.ClearProviders();
//builder.Logging.AddProvider(new LogMigratorProvider()); builder.Logging.AddProvider(new LogMigratorProvider());
// Add services to the container. // Add services to the container.
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
@@ -72,6 +132,23 @@ namespace Moonlight
.AddCheck<NodeHealthCheck>("Nodes") .AddCheck<NodeHealthCheck>("Nodes")
.AddCheck<DaemonHealthCheck>("Daemons"); .AddCheck<DaemonHealthCheck>("Daemons");
// Sentry
if (shouldUseSentry)
{
builder.WebHost.UseSentry(options =>
{
options.Dsn = configService
.Get()
.Moonlight.Sentry.Dsn;
options.Debug = configService.DebugMode;
options.DiagnosticLevel = SentryLevel.Warning;
options.TracesSampleRate = 1.0;
options.DiagnosticLogger = new SentryDiagnosticsLogger(SentryLevel.Warning);
});
}
// Databases // Databases
builder.Services.AddDbContext<DataContext>(); builder.Services.AddDbContext<DataContext>();
@@ -134,15 +211,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>();
@@ -168,6 +242,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>();
@@ -188,6 +263,12 @@ namespace Moonlight
app.UseHsts(); app.UseHsts();
} }
// Sentry
if (shouldUseSentry)
{
app.UseSentryTracing();
}
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseWebSockets(); app.UseWebSockets();
@@ -206,6 +287,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

@@ -1,16 +1,12 @@
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ConfigService ConfigService @inject ResourceService ResourceService
@{
var moonlightConfig = ConfigService
.GetSection("Moonlight");
}
<div class="card card-flush w-lg-650px py-5"> <div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20"> <div class="card-body py-15 py-lg-20">
<div class="mb-14"> <div class="mb-14">
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" class="h-40px"> <img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
</div> </div>
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your account is banned from moonlight</TL></h1> <h1 class="fw-bolder text-gray-900 mb-5"><TL>Your account is banned from moonlight</TL></h1>
<div class="fw-semibold fs-6 text-gray-500 mb-8"> <div class="fw-semibold fs-6 text-gray-500 mb-8">

View File

@@ -1,16 +1,12 @@
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ConfigService ConfigService @inject ResourceService ResourceService
@{
var moonlightConfig = ConfigService
.GetSection("Moonlight");
}
<div class="card card-flush w-lg-650px py-5"> <div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20"> <div class="card-body py-15 py-lg-20">
<div class="mb-14"> <div class="mb-14">
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logolong.png" class="h-40px"> <img alt="Logo" src="@(ResourceService.Image("logolong.png"))" class="h-40px">
</div> </div>
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Your moonlight account is disabled</TL></h1> <h1 class="fw-bolder text-gray-900 mb-5"><TL>Your moonlight account is disabled</TL></h1>
<div class="fw-semibold fs-6 text-gray-500 mb-8"> <div class="fw-semibold fs-6 text-gray-500 mb-8">

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,7 +42,7 @@ else
{ {
if (ConfigService.DebugMode) if (ConfigService.DebugMode)
{ {
Logger.Warn(exception); Logger.Verbose(exception);
} }
if (exception is DisplayException displayException) if (exception is DisplayException displayException)

View File

@@ -0,0 +1,87 @@
@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()
{
try
{
Uri uri = new Uri(await Access.GetLaunchUrl());
Host = uri.Host;
Port = uri.Port;
Username = uri.UserInfo.Split(':')[0];
}
catch (NotImplementedException)
{
Host = "N/A";
Port = -1;
Username = "N/A";
}
}
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>
} }
@@ -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,35 +59,44 @@ 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">
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
</svg>
</span>
<TL>Launch WinSCP</TL> <TL>Launch WinSCP</TL>
</button> </button>
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
<TL>Download WinSCP</TL>
</a>
</li>
<li>
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
<TL>Show connection details</TL>
</button>
</li>
</ul>
</div>
<button type="button" @onclick="CreateFile" class="btn btn-light-primary me-3"> <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>
<button @onclick="CreateFile" class="dropdown-item btn">
<TL>New file</TL> <TL>New file</TL>
</button> </button>
</li>
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3"> <li>
<span class="svg-icon svg-icon-2"> <button @onclick="CreateFolder" class="dropdown-item btn">
<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> <TL>New folder</TL>
</button> </button>
</li>
</ul>
</div>
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/> <FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
} }
@@ -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();
@@ -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)

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

@@ -10,13 +10,13 @@
</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 == 1 ? "active" : "")" href="/admin/system/logs"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/system/sentry">
<TL>Logs</TL> <TL>Sentry</TL>
</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

@@ -4,31 +4,31 @@
@{ @{
var marketingConfig = ConfigService var marketingConfig = ConfigService
.GetSection("Moonlight") .Get()
.GetSection("Marketing"); .Moonlight.Marketing;
} }
<div id="kt_app_footer" class="app-footer"> <div id="kt_app_footer" class="app-footer">
<div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3"> <div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3">
<div class="text-dark order-2 order-md-1"> <div class="text-dark order-2 order-md-1">
<span class="text-muted fw-semibold me-1">2022 - @DateTime.Now.Year©</span> <span class="text-muted fw-semibold me-1">2022 - @DateTime.Now.Year©</span>
<a href="@(marketingConfig.GetValue<string>("Website"))" target="_blank" class="text-gray-800 text-hover-primary"> <a href="@(marketingConfig.Website)" target="_blank" class="text-gray-800 text-hover-primary">
@(marketingConfig.GetValue<string>("BrandName")) @(marketingConfig.BrandName)
</a> </a>
</div> </div>
<ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1"> <ul class="menu menu-gray-600 menu-hover-primary fw-semibold order-1">
<li class="menu-item"> <li class="menu-item">
<a href="@(marketingConfig.GetValue<string>("About"))" target="_blank" class="menu-link px-2"> <a href="@(marketingConfig.About)" target="_blank" class="menu-link px-2">
<TL>About us</TL> <TL>About us</TL>
</a> </a>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<a href="@(marketingConfig.GetValue<string>("Imprint"))" target="_blank" class="menu-link px-2"> <a href="@(marketingConfig.Imprint)" target="_blank" class="menu-link px-2">
<TL>Imprint</TL> <TL>Imprint</TL>
</a> </a>
</li> </li>
<li class="menu-item"> <li class="menu-item">
<a href="@(marketingConfig.GetValue<string>("Privacy"))" target="_blank" class="menu-link px-2"> <a href="@(marketingConfig.Privacy)" target="_blank" class="menu-link px-2">
<TL>Privacy</TL> <TL>Privacy</TL>
</a> </a>
</li> </li>

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">
<div class="@(User.StreamerMode ? "blur" : "")">
@(User.FirstName) @(User.LastName) @(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,10 +1,6 @@
@using Moonlight.App.Services @using Moonlight.App.Services.Files
@inject ConfigService ConfigService @inject ResourceService ResourceService
@{
var moonlightConfig = ConfigService.GetSection("Moonlight");
}
<div id="kt_app_header" class="app-header"> <div id="kt_app_header" class="app-header">
<div class="app-container container-fluid d-flex align-items-stretch justify-content-between"> <div class="app-container container-fluid d-flex align-items-stretch justify-content-between">
@@ -15,7 +11,7 @@
</div> </div>
<div class="d-flex align-items-center flex-grow-1 flex-lg-grow-0"> <div class="d-flex align-items-center flex-grow-1 flex-lg-grow-0">
<a href="/" class="d-lg-none"> <a href="/" class="d-lg-none">
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-30px"/> <img alt="Logo" src="@(ResourceService.Image("logo.svg"))" class="h-30px"/>
</a> </a>
</div> </div>
<div class="d-flex align-items-stretch justify-content-between flex-lg-grow-1" id="kt_app_header_wrapper"> <div class="d-flex align-items-stretch justify-content-between flex-lg-grow-1" id="kt_app_header_wrapper">

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