72 Commits
v1b8 ... v1b13

Author SHA1 Message Date
Marcel Baumgartner
a295354549 Merge pull request #210 from Dannyx1604/patch-1
I got bored again (de_de.lang)
2023-07-07 03:23:31 +02:00
Marcel Baumgartner
749ea5dc8e Merge pull request #211 from Moonlight-Panel/AddTelemetryReporter
Added telemetry reporter
2023-07-07 03:14:06 +02:00
Marcel Baumgartner
f52b9e2951 Added telemetry reporter 2023-07-07 03:06:16 +02:00
Dannyx
d2dbb68967 I got bored again (de_de.lang) 2023-07-06 23:28:08 +02:00
Marcel Baumgartner
d1c9009e9f Merge pull request #209 from Moonlight-Panel/NewVisualConfigEditor
Added a new visual config editor
2023-07-06 16:46:29 +02:00
Marcel Baumgartner
d024a834f9 Added a new visual config editor 2023-07-06 16:46:01 +02:00
Marcel Baumgartner
ab529991fd Fix some javascript not loaded issues
Because all js files are executed in the order they were put into the document some js files were not loaded while starting blazor. this should fix it (hopefully ;) )
2023-07-04 18:06:14 +02:00
Marcel Baumgartner
92705837ba Merge pull request #208 from Moonlight-Panel/RewriteSessionSystem
Rewritten session system to match new standarts and be more performant
2023-07-04 17:51:09 +02:00
Marcel Baumgartner
609d5451f9 Rewritten session system to match new standarts and be more performant 2023-07-04 17:49:27 +02:00
Marcel Baumgartner
2bb2caeeed Merge pull request #207 from Moonlight-Panel/AddIpLogs
Added ip log for register and last visit
2023-07-03 20:17:20 +02:00
Marcel Baumgartner
61db49bfb7 Added ip log for register and last visit 2023-07-03 20:01:34 +02:00
Marcel Baumgartner
a75678d305 Merge pull request #206 from Moonlight-Panel/SmallFixes
Small fixes
2023-07-02 21:33:12 +02:00
Marcel Baumgartner
d418c91efa Fixed js invoke errors 2023-07-02 21:30:34 +02:00
Marcel Baumgartner
7f2da5a55d Updated sweet alert 2 2023-07-02 20:56:10 +02:00
Marcel Baumgartner
5e592ccdcb Added ignore for unexpected dispose errors 2023-07-02 20:51:08 +02:00
Marcel Baumgartner
016f50fb1c Added ignore for json serialize errors 2023-07-02 20:48:47 +02:00
Marcel Baumgartner
fe21668a2b Removed wrong logged warn 2023-07-02 20:44:29 +02:00
Marcel Baumgartner
1aab86a317 Fixed wrong ssl config for nodes 2023-07-02 20:41:31 +02:00
Marcel Baumgartner
243d23d4e2 Fixed repatcha config for empty values 2023-07-02 20:40:47 +02:00
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
159 changed files with 7347 additions and 2779 deletions

1
.gitattributes vendored
View File

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

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Newtonsoft.Json;
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.ApiClients.Modrinth;
@@ -48,7 +47,7 @@ public class ModrinthApiHelper
var request = new RestRequest(url)
{
Timeout = 60 * 15
Timeout = 300000
};
request.AddHeader("Content-Type", "application/json");

View File

@@ -0,0 +1,11 @@
namespace Moonlight.App.ApiClients.Telemetry.Requests;
public class TelemetryData
{
public string AppUrl { get; set; } = "";
public int Servers { get; set; }
public int Nodes { get; set; }
public int Users { get; set; }
public int Databases { get; set; }
public int Webspaces { get; set; }
}

View File

@@ -0,0 +1,52 @@
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.ApiClients.Telemetry;
public class TelemetryApiHelper
{
private readonly RestClient Client;
public TelemetryApiHelper()
{
Client = new();
}
public async Task Post(string resource, object? body)
{
var request = CreateRequest(resource);
request.Method = Method.Post;
request.AddParameter("application/json", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new TelemetryException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
private RestRequest CreateRequest(string resource)
{
var url = "https://telemetry.moonlightpanel.xyz/" + resource;
var request = new RestRequest(url)
{
Timeout = 3000000
};
return request;
}
}

View File

@@ -0,0 +1,32 @@
using System.Runtime.Serialization;
namespace Moonlight.App.ApiClients.Telemetry;
[Serializable]
public class TelemetryException : Exception
{
public int StatusCode { get; set; }
public TelemetryException()
{
}
public TelemetryException(string message, int statusCode) : base(message)
{
StatusCode = statusCode;
}
public TelemetryException(string message) : base(message)
{
}
public TelemetryException(string message, Exception inner) : base(message, inner)
{
}
protected TelemetryException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}

View File

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

View File

@@ -0,0 +1,320 @@
using System.ComponentModel;
using Moonlight.App.Helpers;
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")]
[Description("The url moonlight is accesible with from the internet")]
public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
[JsonProperty("DiscordBotApi")] public DiscordBotApiData 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")]
[Description("The maximum amount of cpu usage in percent a node is allowed to use before the cleanup starts")]
public long Cpu { get; set; } = 90;
[JsonProperty("Memory")]
[Description("The minumum amount of memory in megabytes avaliable before the cleanup starts")]
public long Memory { get; set; } = 8192;
[JsonProperty("Wait")]
[Description("The delay between every cleanup check in minutes")]
public long Wait { get; set; } = 15;
[JsonProperty("Uptime")]
[Description("The maximum uptime of any server in hours before it the server restarted by the cleanup system")]
public long Uptime { get; set; } = 6;
[JsonProperty("Enable")]
[Description("The cleanup system provides a fair way for stopping unused servers and staying stable even with overallocation. A detailed explanation: docs.endelon-hosting.de/erklaerungen/cleanup")]
public bool Enable { get; set; } = false;
[JsonProperty("MinUptime")]
[Description("The minumum uptime of a server in minutes to prevent stopping servers which just started")]
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")]
[Blur]
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 DiscordBotApiData
{
[JsonProperty("Enable")]
[Description("Enable the discord bot api. Currently only DatBot is using this api")]
public bool Enable { get; set; } = false;
[JsonProperty("Token")]
[Description("Specify the token the api client needs to provide")]
[Blur]
public string Token { get; set; } = Guid.NewGuid().ToString();
}
public class DiscordBotData
{
[JsonProperty("Enable")]
[Description("The discord bot can be used to allow customers to manage their servers via discord")]
public bool Enable { get; set; } = false;
[JsonProperty("Token")]
[Description("Your discord bot token goes here")]
[Blur]
public string Token { get; set; } = "discord token here";
[JsonProperty("PowerActions")]
[Description("Enable actions like starting and stopping servers")]
public bool PowerActions { get; set; } = false;
[JsonProperty("SendCommands")]
[Description("Allow users to send commands to their servers")]
public bool SendCommands { get; set; } = false;
}
public class DiscordNotificationsData
{
[JsonProperty("Enable")]
[Description("The discord notification system sends you a message everytime a event like a new support chat message is triggered with usefull data describing the event")]
public bool Enable { get; set; } = false;
[JsonProperty("WebHook")]
[Description("The discord webhook the notifications are being sent to")]
[Blur]
public string WebHook { get; set; } = "http://your-discord-webhook-url";
}
public class DomainsData
{
[JsonProperty("Enable")]
[Description("This enables the domain system")]
public bool Enable { get; set; } = false;
[JsonProperty("AccountId")]
[Description("This option specifies the cloudflare account id")]
public string AccountId { get; set; } = "cloudflare acc id";
[JsonProperty("Email")]
[Description("This specifies the cloudflare email to use for communicating with the cloudflare api")]
public string Email { get; set; } = "cloudflare@acc.email";
[JsonProperty("Key")]
[Description("Your cloudflare api key goes here")]
[Blur]
public string Key { get; set; } = "secret";
}
public class HtmlData
{
[JsonProperty("Headers")] public HeadersData Headers { get; set; } = new();
}
public class HeadersData
{
[JsonProperty("Color")]
[Description("This specifies the color of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
public string Color { get; set; } = "#4b27e8";
[JsonProperty("Description")]
[Description("This specifies the description text of the embed generated by platforms like discord when someone posts a link to your moonlight instance and can also help google to index your moonlight instance correctly")]
public string Description { get; set; } = "the next generation hosting panel";
[JsonProperty("Keywords")]
[Description("To help search engines like google to index your moonlight instance correctly you can specify keywords seperated by a comma here")]
public string Keywords { get; set; } = "moonlight";
[JsonProperty("Title")]
[Description("This specifies the title of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
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")]
[Blur]
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")]
[Description("This overrides the redirect url which would be typicaly the app url")]
public string OverrideUrl { get; set; } = "https://only-for-development.cases";
[JsonProperty("EnableOverrideUrl")]
[Description("This enables the url override")]
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")]
[Blur]
public string ClientSecret { get; set; }
}
public class RatingData
{
[JsonProperty("Enabled")]
[Description("The rating systems shows a user who is registered longer than the set amout of days a popup to rate this platform if he hasnt rated it before")]
public bool Enabled { get; set; } = false;
[JsonProperty("Url")]
[Description("This is the url a user who rated above a set limit is shown to rate you again. Its recommended to put your google or trustpilot rate link here")]
public string Url { get; set; } = "https://link-to-google-or-smth";
[JsonProperty("MinRating")]
[Description("The minimum star count on the rating ranging from 1 to 5")]
public int MinRating { get; set; } = 4;
[JsonProperty("DaysSince")]
[Description("The days a user has to be registered to even be able to get this popup")]
public int DaysSince { get; set; } = 5;
}
public class SecurityData
{
[JsonProperty("Token")]
[Description("This is the moonlight app token. It is used to encrypt and decrypt data and validte tokens and sessions")]
[Blur]
public string Token { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
}
public class ReCaptchaData
{
[JsonProperty("Enable")]
[Description("Enables repatcha at places like the register page. For information how to get your recaptcha credentails go to google.com/recaptcha/about/")]
public bool Enable { get; set; } = false;
[JsonProperty("SiteKey")]
[Blur]
public string SiteKey { get; set; } = "recaptcha site key here";
[JsonProperty("SecretKey")]
[Blur]
public string SecretKey { get; set; } = "recaptcha secret here";
}
public class SentryData
{
[JsonProperty("Enable")]
[Description("Sentry is a way to monitor application crashes and performance issues in real time. Enable this option only if you set a sentry dsn")]
public bool Enable { get; set; } = false;
[JsonProperty("Dsn")]
[Description("The dsn is the key moonlight needs to communicate with your sentry instance")]
[Blur]
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)
{
var config = ConfigService
.GetSection("Moonlight")
.GetSection("Database");
.Get()
.Moonlight.Database;
var connectionString = $"host={config.GetValue<string>("Host")};" +
$"port={config.GetValue<int>("Port")};" +
$"database={config.GetValue<string>("Database")};" +
$"uid={config.GetValue<string>("Username")};" +
$"pwd={config.GetValue<string>("Password")}";
var connectionString = $"host={config.Host};" +
$"port={config.Port};" +
$"database={config.Database};" +
$"uid={config.Username};" +
$"pwd={config.Password}";
optionsBuilder.UseMySql(
connectionString,

View File

@@ -24,6 +24,8 @@ public class User
public string Country { get; set; } = "";
public string ServerListLayoutJson { get; set; } = "";
// States
public UserStatus Status { get; set; } = UserStatus.Unverified;
@@ -31,6 +33,7 @@ public class User
public bool SupportPending { get; set; } = false;
public bool HasRated { get; set; } = false;
public int Rating { get; set; } = 0;
public bool StreamerMode { get; set; } = false;
// Security
public bool TotpEnabled { get; set; } = false;
@@ -50,4 +53,8 @@ public class User
public Subscription? CurrentSubscription { get; set; } = null;
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
public int SubscriptionDuration { get; set; }
// Ip logs
public string RegisterIp { get; set; } = "";
public string LastIp { get; set; } = "";
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -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

@@ -0,0 +1,6 @@
namespace Moonlight.App.Helpers;
public class BlurAttribute : Attribute
{
}

View File

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

View File

@@ -1,5 +1,4 @@
using System.Diagnostics;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
using Moonlight.App.Services;
@@ -67,21 +66,21 @@ public class DatabaseCheckupService
var configService = new ConfigService(new StorageService());
var dateTimeService = new DateTimeService();
var config = configService
.GetSection("Moonlight")
.GetSection("Database");
var config = configService.Get().Moonlight.Database;
var connectionString = $"host={config.GetValue<string>("Host")};" +
$"port={config.GetValue<int>("Port")};" +
$"database={config.GetValue<string>("Database")};" +
$"uid={config.GetValue<string>("Username")};" +
$"pwd={config.GetValue<string>("Password")}";
var connectionString = $"host={config.Host};" +
$"port={config.Port};" +
$"database={config.Database};" +
$"uid={config.Username};" +
$"pwd={config.Password}";
string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql");
Logger.Info($"Saving it to: {file}");
Logger.Info("Starting backup...");
try
{
var sw = new Stopwatch();
sw.Start();
@@ -97,4 +96,15 @@ public class DatabaseCheckupService
sw.Stop();
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");
}
catch (Exception e)
{
Logger.Fatal("-----------------------------------------------");
Logger.Fatal("Unable to create backup for moonlight database");
Logger.Fatal("Moonlight will start anyways in 30 seconds");
Logger.Fatal("-----------------------------------------------");
Logger.Fatal(e);
Thread.Sleep(TimeSpan.FromSeconds(30));
}
}
}

View File

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

View File

@@ -111,7 +111,7 @@ public class WingsFileAccess : FileAccess
request.AddParameter("name", "files");
request.AddParameter("filename", name);
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", () =>
{
return new StreamProgressHelper(dataStream)

View File

@@ -1,9 +1,36 @@
using Moonlight.App.Services;
using System.Text;
using Moonlight.App.Services;
namespace Moonlight.App.Helpers;
public static class Formatter
{
public static string ReplaceEnd(string input, string substringToReplace, string newSubstring)
{
int lastIndexOfSubstring = input.LastIndexOf(substringToReplace);
if (lastIndexOfSubstring >= 0)
{
input = input.Remove(lastIndexOfSubstring, substringToReplace.Length).Insert(lastIndexOfSubstring, newSubstring);
}
return input;
}
public static string ConvertCamelCaseToSpaces(string input)
{
StringBuilder output = new StringBuilder();
foreach (char c in input)
{
if (char.IsUpper(c))
{
output.Append(' ');
}
output.Append(c);
}
return output.ToString().Trim();
}
public static string FormatUptime(double uptime)
{
TimeSpan t = TimeSpan.FromMilliseconds(uptime);

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
using System.Reflection;
namespace Moonlight.App.Helpers;
public class PropBinder
{
private PropertyInfo PropertyInfo;
private object DataObject;
public PropBinder(PropertyInfo propertyInfo, object dataObject)
{
PropertyInfo = propertyInfo;
DataObject = dataObject;
}
public string StringValue
{
get => (string)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public int IntValue
{
get => (int)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public long LongValue
{
get => (long)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public bool BoolValue
{
get => (bool)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public DateTime DateTimeValue
{
get => (DateTime)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public double DoubleValue
{
get => (double)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
}

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.Text;
using Logging.Net;
using Moonlight.App.Helpers.Wings.Data;
using Moonlight.App.Helpers.Wings.Enums;
using Moonlight.App.Helpers.Wings.Events;
@@ -244,6 +243,7 @@ public class WingsConsole : IDisposable
}
}
catch(JsonReaderException){}
catch(JsonSerializationException){}
catch (Exception e)
{
if (!Disconnecting)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Moonlight.App.Helpers;
namespace Moonlight.App.LogMigrator;
@@ -28,19 +27,39 @@ public class LogMigrator : ILogger
switch (logLevel)
{
case LogLevel.Critical:
Logger.Fatal($"[{Name}] {formatter(state, exception)}");
Logger.Fatal(formatter(state, exception));
if(exception != null)
Logger.Fatal(exception);
break;
case LogLevel.Warning:
Logger.Warn($"[{Name}] {formatter(state, exception)}");
Logger.Warn(formatter(state, exception));
if(exception != null)
Logger.Warn(exception);
break;
case LogLevel.Debug:
Logger.Debug($"[{Name}] {formatter(state, exception)}");
Logger.Debug(formatter(state, exception));
if(exception != null)
Logger.Debug(exception);
break;
case LogLevel.Error:
Logger.Error($"[{Name}] {formatter(state, exception)}");
Logger.Error(formatter(state, exception));
if(exception != null)
Logger.Error(exception);
break;
case LogLevel.Information:
Logger.Info($"[{Name}] {formatter(state, exception)}");
Logger.Info(formatter(state, exception));
if(exception != null)
Logger.Info(exception);
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,16 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Models.Misc;
public class Session
{
public string Ip { get; set; } = "N/A";
public string Url { get; set; } = "N/A";
public string Device { get; set; } = "N/A";
public User? User { get; set; }
public DateTime CreatedAt { get; set; }
public NavigationManager Navigation { get; set; }
public AlertService AlertService { get; set; }
}

View File

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

View File

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

View File

@@ -1,37 +0,0 @@
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Repositories;
public class SessionRepository
{
private readonly List<Session> Sessions;
public SessionRepository()
{
Sessions = new();
}
public Session[] Get()
{
lock (Sessions)
{
return Sessions.ToArray();
}
}
public void Add(Session session)
{
lock (Sessions)
{
Sessions.Add(session);
}
}
public void Delete(Session session)
{
lock (Sessions)
{
Sessions.Remove(session);
}
}
}

View File

@@ -1,5 +1,4 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MineStatLib;
using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings;
@@ -44,15 +43,15 @@ public class CleanupService
CompletedAt = DateTimeService.GetCurrent();
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");
return;
}
Timer = new(TimeSpan.FromMinutes(config.GetValue<int>("Wait")));
Timer = new(TimeSpan.FromMinutes(config.Wait));
Task.Run(Run);
}
@@ -64,12 +63,12 @@ public class CleanupService
IsRunning = true;
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 minMemory = config.GetValue<int>("Memory");
var maxUptime = config.GetValue<int>("Uptime");
var minUptime = config.GetValue<int>("MinUptime");
var maxCpu = config.Cpu;
var minMemory = config.Memory;
var maxUptime = config.Uptime;
var minUptime = config.MinUptime;
var nodeRepository = scope.ServiceProvider.GetRequiredService<NodeRepository>();
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();

View File

@@ -1,8 +1,8 @@
using Discord;
using Discord.Webhook;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Helpers;
using Moonlight.App.Services.Files;
namespace Moonlight.App.Services.Background;
@@ -22,14 +22,14 @@ public class DiscordNotificationService
Event = eventSystem;
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");
Client = new(config.GetValue<string>("WebHook"));
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
Client = new(config.WebHook);
AppUrl = configService.Get().Moonlight.AppUrl;
Event.On<User>("supportChat.new", this, OnNewSupportChat);
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

@@ -0,0 +1,62 @@
using Moonlight.App.ApiClients.Telemetry;
using Moonlight.App.ApiClients.Telemetry.Requests;
using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
namespace Moonlight.App.Services.Background;
public class TelemetryService
{
private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly ConfigService ConfigService;
public TelemetryService(
ConfigService configService,
IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
ConfigService = configService;
if(!ConfigService.DebugMode)
Task.Run(Run);
}
private async Task Run()
{
var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));
while (true)
{
using var scope = ServiceScopeFactory.CreateScope();
var serversRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
var nodesRepo = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
var usersRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
var databaseRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
var apiHelper = scope.ServiceProvider.GetRequiredService<TelemetryApiHelper>();
try
{
await apiHelper.Post("telemetry", new TelemetryData()
{
Servers = serversRepo.Get().Count(),
Databases = databaseRepo.Get().Count(),
Nodes = nodesRepo.Get().Count(),
Users = usersRepo.Get().Count(),
Webspaces = webspacesRepo.Get().Count(),
AppUrl = ConfigService.Get().Moonlight.AppUrl
});
}
catch (Exception e)
{
Logger.Warn("Error sending telemetry");
Logger.Warn(e);
}
await timer.WaitForNextTickAsync();
}
}
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using Discord;
using Discord.WebSocket;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
@@ -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);
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);
return;
@@ -58,7 +57,7 @@ public class ServerListCommand : BaseModule
components.WithButton("Panel",
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}");
url: $"{ConfigService.Get().Moonlight.AppUrl}");
if (servers.Count > 25)
{

View File

@@ -1,8 +1,7 @@
using System.Diagnostics;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Logging.Net;
using Moonlight.App.Helpers;
using Moonlight.App.Services.DiscordBot.Commands;
using Moonlight.App.Services.DiscordBot.Modules;
@@ -45,10 +44,10 @@ public DiscordBotService(
ServiceScope = ServiceScopeFactory.CreateScope();
var discordConfig = ConfigService
.GetSection("Moonlight")
.GetSection("DiscordBot");
.Get()
.Moonlight.DiscordBot;
if (!discordConfig.GetValue<bool>("Enable"))
if (!discordConfig.Enable)
return;
Client.Log += Log;
@@ -68,7 +67,7 @@ public DiscordBotService(
await ActivityStatusModule.UpdateActivityStatusList();
await Client.LoginAsync(TokenType.Bot, discordConfig.GetValue<string>("Token"));
await Client.LoginAsync(TokenType.Bot, discordConfig.Token);
await Client.StartAsync();
await Task.Delay(-1);

View File

@@ -87,8 +87,8 @@ public class EmbedBuilderModule : BaseModule
int[] randomNumbers = new int[] { 1, 3, 8, 11, 20 };
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 Discord.WebSocket;
using Logging.Net;
using Moonlight.App.Helpers;
namespace Moonlight.App.Services.DiscordBot.Modules;

View File

@@ -1,8 +1,8 @@
using Discord;
using Discord.WebSocket;
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
@@ -72,7 +72,7 @@ public class ServerListComponentHandlerModule : BaseModule
// stopping
// offline
// 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);
await component.RespondAsync(embed: embed.Build(), ephemeral: true);
@@ -80,7 +80,7 @@ public class ServerListComponentHandlerModule : BaseModule
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);
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);
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);
return;
@@ -332,7 +332,7 @@ public class ServerListComponentHandlerModule : BaseModule
components.WithButton("Panel",
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}");
url: $"{ConfigService.Get().Moonlight.AppUrl}");
components.WithButton("Previous-page",
emote: Emote.Parse("<:ArrowLeft:1101547474180649030>"),
@@ -378,7 +378,7 @@ public class ServerListComponentHandlerModule : BaseModule
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("Restart", style: ButtonStyle.Primary, customId: $"Sm.Restart.{server.Id}", disabled: false);
@@ -389,14 +389,14 @@ public class ServerListComponentHandlerModule : BaseModule
components.WithButton("Way2Server",
emote: Emote.Parse("<a:Earth:1092814004113657927>"),
style: ButtonStyle.Link,
url: $"{ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl")}/server/{server.Uuid}");
url: $"{ConfigService.Get().Moonlight.AppUrl}/server/{server.Uuid}");
components.WithButton("Update",
emote: Emote.Parse("<:refresh:1101547898803605605>"),
style: ButtonStyle.Secondary,
customId: $"Sm.Update.{server.Id}");
if (ConfigService.GetSection("Moonlight").GetSection("DiscordBot").GetValue<bool>("SendCommands"))
if (ConfigService.Get().Moonlight.DiscordBot.SendCommands)
{
components.WithButton("SendCommand",
emote: Emote.Parse("<:Console:1101547358157819944>"),

View File

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

View File

@@ -8,7 +8,7 @@ public class ResourceService
public ResourceService(ConfigService configService)
{
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
AppUrl = configService.Get().Moonlight.AppUrl;
}
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;
@@ -16,6 +15,7 @@ public class StorageService
Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "resources"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
{

View File

@@ -1,21 +1,40 @@
using CurrieTechnologies.Razor.SweetAlert2;
using Microsoft.JSInterop;
namespace Moonlight.App.Services.Interop;
public class AlertService
{
private readonly SweetAlertService SweetAlertService;
private readonly SmartTranslateService SmartTranslateService;
private readonly IJSRuntime JsRuntime;
private SweetAlertService? SweetAlertService;
public AlertService(SweetAlertService service, SmartTranslateService smartTranslateService)
public AlertService(SmartTranslateService smartTranslateService, IJSRuntime jsRuntime)
{
SweetAlertService = service;
SmartTranslateService = smartTranslateService;
JsRuntime = jsRuntime;
}
// We create the swal service here and not using the dependency injection
// because it initializes when instantiated which leads to js invoke errors
private Task EnsureService()
{
if (SweetAlertService == null)
{
SweetAlertService = new(JsRuntime, new()
{
Theme = SweetAlertTheme.Dark
});
}
return Task.CompletedTask;
}
public async Task Info(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,
@@ -30,7 +49,9 @@ public class AlertService
public async Task Success(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,
@@ -45,7 +66,9 @@ public class AlertService
public async Task Warning(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,
@@ -60,7 +83,9 @@ public class AlertService
public async Task Error(string title, string desciption)
{
await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,
@@ -75,7 +100,9 @@ public class AlertService
public async Task<bool> YesNo(string title, string desciption, string yesText, string noText)
{
var result = await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
var result = await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,
@@ -91,7 +118,9 @@ public class AlertService
public async Task<string?> Text(string title, string desciption, string setValue)
{
var result = await SweetAlertService.FireAsync(new SweetAlertOptions()
await EnsureService();
var result = await SweetAlertService!.FireAsync(new SweetAlertOptions()
{
Title = title,
Text = desciption,

View File

@@ -25,16 +25,15 @@ public class ReCaptchaService
ConfigService = configService;
var recaptchaConfig = ConfigService
.GetSection("Moonlight")
.GetSection("Security")
.GetSection("ReCaptcha");
.Get()
.Moonlight.Security.ReCaptcha;
Enable = recaptchaConfig.GetValue<bool>("Enable");
Enable = recaptchaConfig.Enable;
if (Enable)
{
SiteKey = recaptchaConfig.GetValue<string>("SiteKey");
SecretKey = recaptchaConfig.GetValue<string>("SecretKey");
SiteKey = recaptchaConfig.SiteKey;
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.Exceptions;
using Moonlight.App.Helpers;
@@ -18,14 +17,14 @@ public class MailService
public MailService(ConfigService configService)
{
var mailConfig = configService
.GetSection("Moonlight")
.GetSection("Mail");
.Get()
.Moonlight.Mail;
Server = mailConfig.GetValue<string>("Server");
Password = mailConfig.GetValue<string>("Password");
Email = mailConfig.GetValue<string>("Email");
Port = mailConfig.GetValue<int>("Port");
Ssl = mailConfig.GetValue<bool>("Ssl");
Server = mailConfig.Server;
Password = mailConfig.Password;
Email = mailConfig.Email;
Port = mailConfig.Port;
Ssl = mailConfig.Ssl;
}
public async Task SendMail(

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,7 @@ using JWT.Builder;
using JWT.Exceptions;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services;
@@ -14,15 +12,12 @@ public class OneTimeJwtService
{
private readonly ConfigService ConfigService;
private readonly RevokeRepository RevokeRepository;
private readonly SecurityLogService SecurityLogService;
public OneTimeJwtService(ConfigService configService,
RevokeRepository revokeRepository,
SecurityLogService securityLogService)
RevokeRepository revokeRepository)
{
ConfigService = configService;
RevokeRepository = revokeRepository;
SecurityLogService = securityLogService;
}
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
@@ -30,10 +25,9 @@ public class OneTimeJwtService
var opt = new Dictionary<string, string>();
options.Invoke(opt);
string secret = ConfigService
.GetSection("Moonlight")
.GetSection("Security")
.GetValue<string>("Token");
var secret = ConfigService
.Get()
.Moonlight.Security.Token;
var id = StringHelper.GenerateString(16);
@@ -60,10 +54,9 @@ public class OneTimeJwtService
public async Task<Dictionary<string, string>?> Validate(string token)
{
string secret = ConfigService
.GetSection("Moonlight")
.GetSection("Security")
.GetValue<string>("Token");
var secret = ConfigService
.Get()
.Moonlight.Security.Token;
string json;
@@ -76,10 +69,7 @@ public class OneTimeJwtService
}
catch (SignatureVerificationException)
{
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x =>
{
x.Add<string>(token);
});
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
return null;
}
catch (Exception e)

View File

@@ -26,12 +26,12 @@ public class RatingService
Event = eventSystem;
UserRepository = userRepository;
var config = configService.GetSection("Moonlight").GetSection("Rating");
var config = configService.Get().Moonlight.Rating;
Enabled = config.GetValue<bool>("Enabled");
Url = config.GetValue<string>("Url");
MinRating = config.GetValue<int>("MinRating");
DaysSince = config.GetValue<int>("DaysSince");
Enabled = config.Enabled;
Url = config.Url;
MinRating = config.MinRating;
DaysSince = config.DaysSince;
}
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.Requests;
using Moonlight.App.ApiClients.Wings.Resources;
@@ -13,7 +12,6 @@ using Moonlight.App.Helpers.Wings;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
using Moonlight.App.Services.LogServices;
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
namespace Moonlight.App.Services;
@@ -30,9 +28,6 @@ public class ServerService
private readonly UserService UserService;
private readonly ConfigService ConfigService;
private readonly WingsJwtHelper WingsJwtHelper;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService;
private readonly NodeService NodeService;
private readonly DateTimeService DateTimeService;
private readonly EventSystem Event;
@@ -46,9 +41,6 @@ public class ServerService
UserService userService,
ConfigService configService,
WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ErrorLogService errorLogService,
NodeService nodeService,
NodeAllocationRepository nodeAllocationRepository,
DateTimeService dateTimeService,
@@ -63,9 +55,6 @@ public class ServerService
UserService = userService;
ConfigService = configService;
WingsJwtHelper = wingsJwtHelper;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
ErrorLogService = errorLogService;
NodeService = nodeService;
NodeAllocationRepository = nodeAllocationRepository;
DateTimeService = dateTimeService;
@@ -90,10 +79,20 @@ public class ServerService
{
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,
$"api/servers/{server.Uuid}"
);
});
return result;
}
public async Task SetPowerState(Server s, PowerSignal signal)
@@ -107,11 +106,7 @@ public class ServerService
Action = rawSignal
});
await AuditLogService.Log(AuditLogType.ChangePowerState, x =>
{
x.Add<Server>(server.Uuid);
x.Add<PowerSignal>(rawSignal);
});
//TODO: AuditLog
}
public async Task<ServerBackup> CreateBackup(Server server)
@@ -124,7 +119,8 @@ public class ServerService
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(),
CreatedAt = DateTimeService.GetCurrent(),
Created = false
@@ -140,12 +136,7 @@ public class ServerService
Ignore = ""
});
await AuditLogService.Log(AuditLogType.CreateBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
});
//TODO: AuditLog
return backup;
}
@@ -182,12 +173,7 @@ public class ServerService
Adapter = "wings"
});
await AuditLogService.Log(AuditLogType.RestoreBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
//TODO: AuditLog
}
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
@@ -200,8 +186,15 @@ public class ServerService
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);
});
}
catch (WingsException e)
{
@@ -220,13 +213,7 @@ public class ServerService
await Event.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(backup.Uuid);
}
);
//TODO: AuditLog
}
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
@@ -239,12 +226,7 @@ public class ServerService
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
});
await AuditLogService.Log(AuditLogType.DownloadBackup,
x =>
{
x.Add<Server>(server.Uuid);
x.Add<ServerBackup>(serverBackup.Uuid);
});
//TODO: AuditLog
if (server.Node.Ssl)
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
@@ -293,7 +275,8 @@ public class ServerService
freeAllocations = NodeAllocationRepository
.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();
}
catch (Exception)
@@ -321,7 +304,8 @@ public class ServerService
Allocations = freeAllocations.ToList(),
Backups = new(),
OverrideStartup = "",
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default)
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default),
Installing = true
};
foreach (var imageVariable in image.Variables)
@@ -339,24 +323,27 @@ public class ServerService
var newServerData = ServerRepository.Add(server);
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()
{
Uuid = newServerData.Uuid,
StartOnCompletion = false
});
});
await AuditLogService.Log(AuditLogType.CreateServer, x => { x.Add<Server>(newServerData.Uuid); });
//TODO: AuditLog
return newServerData;
}
catch (Exception e)
{
await ErrorLogService.Log(e, x =>
{
x.Add<Server>(newServerData.Uuid);
x.Add<Node>(node.Id);
});
Logger.Error("Error creating server on wings");
Logger.Error(e);
ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff
@@ -373,7 +360,7 @@ public class ServerService
server.Installing = true;
ServerRepository.Update(server);
await AuditLogService.Log(AuditLogType.ReinstallServer, x => { x.Add<Server>(server.Uuid); });
//TODO: AuditLog
}
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
@@ -382,7 +369,7 @@ public class ServerService
if (server == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x => { x.Add<int>(id); });
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Server not found");
}
@@ -408,8 +395,6 @@ public class ServerService
public async Task Delete(Server s)
{
throw new DisplayException("Deleting servers is currently disabled");
var backups = await GetBackups(s);
foreach (var backup in backups)
@@ -430,7 +415,21 @@ public class ServerService
.Include(x => x.Node)
.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);
});
}
catch (WingsException e)
{
if (e.StatusCode != 404)
throw;
}
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;

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Services.Sessions;
public class SessionClientService
{
public readonly Guid Uuid = Guid.NewGuid();
public readonly DateTime CreateTimestamp = DateTime.UtcNow;
public User? User { get; private set; }
public readonly IdentityService IdentityService;
public readonly AlertService AlertService;
public readonly NavigationManager NavigationManager;
public readonly IJSRuntime JsRuntime;
private readonly SessionServerService SessionServerService;
private readonly Repository<User> UserRepository;
public SessionClientService(
IdentityService identityService,
AlertService alertService,
NavigationManager navigationManager,
IJSRuntime jsRuntime,
SessionServerService sessionServerService,
Repository<User> userRepository)
{
IdentityService = identityService;
AlertService = alertService;
NavigationManager = navigationManager;
JsRuntime = jsRuntime;
SessionServerService = sessionServerService;
UserRepository = userRepository;
}
public async Task Start()
{
User = await IdentityService.Get();
if (User != null) // Track users last visit
{
User.LastVisitedAt = DateTime.UtcNow;
UserRepository.Update(User);
}
await SessionServerService.Register(this);
}
public async Task Stop()
{
await SessionServerService.UnRegister(this);
}
}

View File

@@ -0,0 +1,64 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
namespace Moonlight.App.Services.Sessions;
public class SessionServerService
{
private readonly List<SessionClientService> Sessions = new();
private readonly EventSystem Event;
public SessionServerService(EventSystem eventSystem)
{
Event = eventSystem;
}
public async Task Register(SessionClientService sessionClientService)
{
lock (Sessions)
{
if(!Sessions.Contains(sessionClientService))
Sessions.Add(sessionClientService);
}
await Event.Emit("sessions.add", sessionClientService);
}
public async Task UnRegister(SessionClientService sessionClientService)
{
lock (Sessions)
{
if(Sessions.Contains(sessionClientService))
Sessions.Remove(sessionClientService);
}
await Event.Emit("sessions.remove", sessionClientService);
}
public Task<SessionClientService[]> GetSessions()
{
lock (Sessions)
{
return Task.FromResult(Sessions.ToArray());
}
}
public async Task ReloadUserSessions(User user)
{
var sessions = await GetSessions();
foreach (var session in sessions)
{
if (session.User != null && session.User.Id == user.Id)
{
try
{
session.NavigationManager.NavigateTo(session.NavigationManager.Uri, true);
}
catch (Exception)
{
// ignore
}
}
}
}
}

View File

@@ -1,83 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Services.Sessions;
public class SessionService
{
private readonly SessionRepository SessionRepository;
private Repository<User> UserRepository;
private readonly IdentityService IdentityService;
private readonly NavigationManager NavigationManager;
private readonly AlertService AlertService;
private readonly DateTimeService DateTimeService;
private Session? OwnSession;
public SessionService(
SessionRepository sessionRepository,
IdentityService identityService,
NavigationManager navigationManager,
AlertService alertService,
DateTimeService dateTimeService,
Repository<User> userRepository)
{
SessionRepository = sessionRepository;
IdentityService = identityService;
NavigationManager = navigationManager;
AlertService = alertService;
DateTimeService = dateTimeService;
UserRepository = userRepository;
}
public async Task Register()
{
var user = await IdentityService.Get();
OwnSession = new Session()
{
Ip = IdentityService.GetIp(),
Url = NavigationManager.Uri,
Device = IdentityService.GetDevice(),
CreatedAt = DateTimeService.GetCurrent(),
User = user,
Navigation = NavigationManager,
AlertService = AlertService
};
SessionRepository.Add(OwnSession);
if (user != null) // Track last session init of user as last visited timestamp
{
user.LastVisitedAt = DateTimeService.GetCurrent();
UserRepository.Update(user);
}
}
public void Refresh()
{
OwnSession.Url = NavigationManager.Uri;
}
public void Close()
{
SessionRepository.Delete(OwnSession);
}
public Session[] GetAll()
{
return SessionRepository.Get();
}
public void ReloadUserSessions(User user)
{
foreach (var session in SessionRepository.Get())
{
if(session.User != null && session.User.Id == user.Id)
session.Navigation.NavigateTo(session.Navigation.Uri, true);
}
}
}

View File

@@ -28,13 +28,12 @@ public class SmartDeployService
public async Task<Node?> GetNode()
{
var config = ConfigService
.GetSection("Moonlight")
.GetSection("SmartDeploy")
.GetSection("Server");
.Get()
.Moonlight.SmartDeploy.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);
}

View File

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

View File

@@ -1,6 +1,5 @@
using Logging.Net;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Sessions;
@@ -18,13 +17,13 @@ public class StatisticsCaptureService
DateTimeService = dateTimeService;
var config = configService
.GetSection("Moonlight")
.GetSection("Statistics");
.Get()
.Moonlight.Statistics;
if(!config.GetValue<bool>("Enabled"))
if(!config.Enabled)
return;
var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait"));
var period = TimeSpan.FromMinutes(config.Wait);
Timer = new(period);
Logger.Info("Starting statistics system");
@@ -37,8 +36,6 @@ public class StatisticsCaptureService
{
while (await Timer.WaitForNextTickAsync())
{
Logger.Warn("Creating statistics");
using var scope = ServiceScopeFactory.CreateScope();
var statisticsRepo = scope.ServiceProvider.GetRequiredService<Repository<StatisticsData>>();
@@ -47,7 +44,7 @@ public class StatisticsCaptureService
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionService>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionServerService>();
void AddEntry(string chart, int value)
{
@@ -64,10 +61,8 @@ public class StatisticsCaptureService
AddEntry("domainsCount", domainsRepo.Get().Count());
AddEntry("webspacesCount", webspacesRepo.Get().Count());
AddEntry("databasesCount", databasesRepo.Get().Count());
AddEntry("sessionsCount", sessionService.GetAll().Length);
AddEntry("sessionsCount", (await sessionService.GetSessions()).Length);
}
Logger.Log("Statistics are weird");
}
catch (Exception e)
{

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Mail;
using Moonlight.App.Services.Sessions;
@@ -15,8 +14,6 @@ public class UserService
{
private readonly UserRepository UserRepository;
private readonly TotpService TotpService;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly MailService MailService;
private readonly IdentityService IdentityService;
private readonly IpLocateService IpLocateService;
@@ -28,8 +25,6 @@ public class UserService
UserRepository userRepository,
TotpService totpService,
ConfigService configService,
SecurityLogService securityLogService,
AuditLogService auditLogService,
MailService mailService,
IdentityService identityService,
IpLocateService ipLocateService,
@@ -37,17 +32,14 @@ public class UserService
{
UserRepository = userRepository;
TotpService = totpService;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
MailService = mailService;
IdentityService = identityService;
IpLocateService = ipLocateService;
DateTimeService = dateTimeService;
JwtSecret = configService
.GetSection("Moonlight")
.GetSection("Security")
.GetValue<string>("Token");
.Get()
.Moonlight.Security.Token;
}
public async Task<string> Register(string email, string password, string firstname, string lastname)
@@ -80,15 +72,14 @@ public class UserService
TotpEnabled = false,
TotpSecret = "",
UpdatedAt = DateTimeService.GetCurrent(),
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
LastIp = IdentityService.GetIp(),
RegisterIp = IdentityService.GetIp()
});
await MailService.SendMail(user!, "register", values => {});
await AuditLogService.Log(AuditLogType.Register, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
return await GenerateToken(user);
}
@@ -102,11 +93,7 @@ public class UserService
if (user == null)
{
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("Email and password combination not found");
}
@@ -115,11 +102,7 @@ public class UserService
return user.TotpEnabled;
}
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("Email and password combination not found");;
}
@@ -144,28 +127,18 @@ public class UserService
if (totpCodeValid)
{
await AuditLogService.Log(AuditLogType.Login, x =>
{
x.Add<User>(email);
});
//TODO: AuditLog
return await GenerateToken(user, true);
}
else
{
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
{
x.Add<User>(email);
x.Add<string>(password);
});
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
throw new DisplayException("2FA code invalid");
}
}
else
{
await AuditLogService.Log(AuditLogType.Login, x =>
{
x.Add<User>(email);
});
//TODO: AuditLog
return await GenerateToken(user!, true);
}
}
@@ -178,10 +151,7 @@ public class UserService
if (isSystemAction)
{
await AuditLogService.LogSystem(AuditLogType.ChangePassword, x=>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
}
else
{
@@ -194,10 +164,7 @@ public class UserService
values.Add("Location", location);
});
await AuditLogService.Log(AuditLogType.ChangePassword, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
}
}
@@ -207,28 +174,18 @@ public class UserService
if (user == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
{
x.Add<int>(id);
});
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Invalid username");
}
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{
await AuditLogService.LogSystem(AuditLogType.Login, x =>
{
x.Add<User>(user.Email);
});
//TODO: AuditLog
return user;
}
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
{
x.Add<int>(id);
x.Add<string>(password);
});
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
throw new Exception("Invalid userid or password");
}
@@ -271,7 +228,7 @@ public class UserService
var newPassword = StringHelper.GenerateString(16);
await ChangePassword(user, newPassword, true);
await AuditLogService.Log(AuditLogType.PasswordReset, x => {});
//TODO: AuditLog
var location = await IpLocateService.GetLocation();

View File

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

View File

@@ -18,7 +18,7 @@
<PackageReference Include="BlazorMonaco" Version="2.1.0" />
<PackageReference Include="BlazorTable" Version="1.17.0" />
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.5.0" />
<PackageReference Include="Discord.Net" Version="3.10.0" />
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
<PackageReference Include="DnsClient" Version="1.7.0" />
@@ -26,7 +26,6 @@
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
<PackageReference Include="JWT" Version="10.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.27.2" />
<PackageReference Include="Logging.Net" Version="1.1.3" />
<PackageReference Include="MailKit" Version="4.0.0" />
<PackageReference Include="Mappy.Net" Version="1.0.2" />
<PackageReference Include="Markdig" Version="0.31.0" />
@@ -47,6 +46,12 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
<PackageReference Include="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="UAParser" Version="3.1.47" />
<PackageReference Include="XtermBlazor" Version="1.8.1" />
@@ -98,4 +103,12 @@
<AdditionalFiles Include="Shared\Views\Server\Settings\ServerResetSetting.razor" />
</ItemGroup>
<ItemGroup>
<Content Update="storage\configs\config.json.bak">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -11,12 +11,12 @@
@{
var headerConfig = ConfigService
.GetSection("Moonlight")
.GetSection("Html")
.GetSection("Headers");
.Get()
.Moonlight.Html.Headers;
var moonlightConfig = ConfigService
.GetSection("Moonlight");
.Get()
.Moonlight;
}
<!DOCTYPE html>
@@ -26,16 +26,16 @@
<meta property="og:locale" content="de_DE"/>
<meta property="og:type" content="article"/>
<meta content="@(headerConfig.GetValue<string>("Title"))" property="og:title"/>
<meta content="@(headerConfig.GetValue<string>("Description"))" property="og:description"/>
<meta content="@(moonlightConfig.GetValue<string>("AppUrl"))" property="og:url"/>
<meta content="@(moonlightConfig.GetValue<string>("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.Title)" property="og:title"/>
<meta content="@(headerConfig.Description)" property="og:description"/>
<meta content="@(moonlightConfig.AppUrl)" property="og:url"/>
<meta content="@(moonlightConfig.AppUrl)/api/moonlight/resources/images/logolong.png" property="og:image"/>
<meta content="@(headerConfig.Color)" data-react-helmet="true" name="theme-color"/>
<meta content="@(headerConfig.GetValue<string>("Description"))" name="description"/>
<meta content="@(headerConfig.GetValue<string>("Keywords"))" name="keywords"/>
<meta content="@(headerConfig.Description)" name="description"/>
<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"/>
@@ -75,7 +75,7 @@
<div id="flashbang" class="flashbanglight"></div>
<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;
@@ -96,13 +96,14 @@
</div>
</div>
<script src="/_framework/blazor.server.js"></script>
<script src="/assets/plugins/global/plugins.bundle.js"></script>
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@@shopify/draggable@1.0.0-beta.11/lib/draggable.bundle.js"></script>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>
@@ -122,5 +123,7 @@ moonlight.loading.registerXterm();
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
<script src="/_framework/blazor.server.js"></script>
</body>
</html>

View File

@@ -2,11 +2,11 @@ using BlazorDownloadFile;
using BlazorTable;
using CurrieTechnologies.Razor.SweetAlert2;
using HealthChecks.UI.Client;
using Logging.Net;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Modrinth;
using Moonlight.App.ApiClients.Paper;
using Moonlight.App.ApiClients.Telemetry;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.Database;
using Moonlight.App.Diagnostics.HealthChecks;
@@ -24,13 +24,15 @@ using Moonlight.App.Services.Background;
using Moonlight.App.Services.DiscordBot;
using Moonlight.App.Services.Files;
using Moonlight.App.Services.Interop;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Mail;
using Moonlight.App.Services.Minecraft;
using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.Sessions;
using Moonlight.App.Services.Statistics;
using Moonlight.App.Services.SupportChat;
using Sentry;
using Serilog;
using Serilog.Events;
namespace Moonlight
{
@@ -38,14 +40,72 @@ namespace Moonlight
{
public static async Task Main(string[] args)
{
Logger.UsedLogger = new CacheLogger();
// This will also copy all default config files
var configService = new ConfigService(new StorageService());
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("Running pre-init tasks");
// This will also copy all default config files
var configService = new ConfigService(new StorageService());
var databaseCheckupService = new DatabaseCheckupService(configService);
await databaseCheckupService.Perform();
@@ -54,8 +114,8 @@ namespace Moonlight
// Switch to logging.net injection
// TODO: Enable in production
//builder.Logging.ClearProviders();
//builder.Logging.AddProvider(new LogMigratorProvider());
builder.Logging.ClearProviders();
builder.Logging.AddProvider(new LogMigratorProvider());
// Add services to the container.
builder.Services.AddRazorPages();
@@ -72,11 +132,27 @@ namespace Moonlight
.AddCheck<NodeHealthCheck>("Nodes")
.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
builder.Services.AddDbContext<DataContext>();
// Repositories
builder.Services.AddSingleton<SessionRepository>();
builder.Services.AddScoped<UserRepository>();
builder.Services.AddScoped<NodeRepository>();
builder.Services.AddScoped<ServerRepository>();
@@ -103,7 +179,6 @@ namespace Moonlight
builder.Services.AddScoped<CookieService>();
builder.Services.AddScoped<IdentityService>();
builder.Services.AddScoped<IpLocateService>();
builder.Services.AddScoped<SessionService>();
builder.Services.AddScoped<AlertService>();
builder.Services.AddScoped<SmartTranslateService>();
builder.Services.AddScoped<UserService>();
@@ -134,15 +209,15 @@ namespace Moonlight
builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddScoped<DynamicBackgroundService>();
builder.Services.AddScoped<ServerAddonPluginService>();
builder.Services.AddScoped<KeyListenerService>();
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();
builder.Services.AddScoped<SessionClientService>();
builder.Services.AddSingleton<SessionServerService>();
// Loggers
builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<ErrorLogService>();
builder.Services.AddScoped<LogService>();
builder.Services.AddScoped<MailService>();
builder.Services.AddSingleton<TrashMailDetectorService>();
@@ -162,19 +237,21 @@ namespace Moonlight
builder.Services.AddScoped<DaemonApiHelper>();
builder.Services.AddScoped<CloudPanelApiHelper>();
builder.Services.AddScoped<ModrinthApiHelper>();
builder.Services.AddScoped<TelemetryApiHelper>();
// Background services
builder.Services.AddSingleton<DiscordBotService>();
builder.Services.AddSingleton<StatisticsCaptureService>();
builder.Services.AddSingleton<DiscordNotificationService>();
builder.Services.AddSingleton<CleanupService>();
builder.Services.AddSingleton<MalwareScanService>();
builder.Services.AddSingleton<TelemetryService>();
// Other
builder.Services.AddSingleton<MoonlightService>();
// Third party services
builder.Services.AddBlazorTable();
builder.Services.AddSweetAlert2(options => { options.Theme = SweetAlertTheme.Dark; });
builder.Services.AddBlazorContextMenu();
builder.Services.AddBlazorDownloadFile();
@@ -188,6 +265,12 @@ namespace Moonlight
app.UseHsts();
}
// Sentry
if (shouldUseSentry)
{
app.UseSentryTracing();
}
app.UseStaticFiles();
app.UseRouting();
app.UseWebSockets();
@@ -206,6 +289,8 @@ namespace Moonlight
_ = app.Services.GetRequiredService<DiscordBotService>();
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
_ = app.Services.GetRequiredService<DiscordNotificationService>();
_ = app.Services.GetRequiredService<MalwareScanService>();
_ = app.Services.GetRequiredService<TelemetryService>();
_ = app.Services.GetRequiredService<MoonlightService>();

View File

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

View File

@@ -1,16 +1,12 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ConfigService ConfigService
@{
var moonlightConfig = ConfigService
.GetSection("Moonlight");
}
@inject ResourceService ResourceService
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<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>
<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">

View File

@@ -1,16 +1,12 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Files
@inject ConfigService ConfigService
@{
var moonlightConfig = ConfigService
.GetSection("Moonlight");
}
@inject ResourceService ResourceService
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<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>
<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">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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