Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11708fbc3b | ||
|
|
daeb4dd5b9 | ||
|
|
daba4cba04 | ||
|
|
1cd0f0f96f | ||
|
|
6a30db07a7 | ||
|
|
6c8754d008 | ||
|
|
356ba94592 | ||
|
|
d3b55d155b | ||
|
|
0015001d7c | ||
|
|
0a86aa8aa4 | ||
|
|
74d4ee729d | ||
|
|
178ff36e86 | ||
|
|
f852df5807 | ||
|
|
90f4b04857 | ||
|
|
244e87ed18 | ||
|
|
80ea5a543f | ||
|
|
5baba05f5f | ||
|
|
591da6de5c | ||
|
|
c1ddff4ae3 | ||
|
|
67d78d7104 | ||
|
|
52f4b00f84 | ||
|
|
8f028e2ac6 | ||
|
|
5bd6f15203 | ||
|
|
4c39ad6170 | ||
|
|
12392d4f47 | ||
|
|
b75147e4c0 | ||
|
|
8f9508f30b | ||
|
|
428e2668d3 | ||
|
|
c1cfb35c86 | ||
|
|
d6777c463e | ||
|
|
f9126bffe0 | ||
|
|
0488e83a38 | ||
|
|
d87ddc90e3 | ||
|
|
151bc82998 | ||
|
|
e4c21c74a5 | ||
|
|
13741a2be9 | ||
|
|
c866e89b72 | ||
|
|
8be93bc53c | ||
|
|
384b6a3e7d | ||
|
|
ba2de54c60 | ||
|
|
bd5567e24f | ||
|
|
b8e39824b5 | ||
|
|
d8c9bdbd8d | ||
|
|
80eb210af0 | ||
|
|
a295354549 | ||
|
|
749ea5dc8e | ||
|
|
f52b9e2951 | ||
|
|
d2dbb68967 | ||
|
|
d1c9009e9f | ||
|
|
d024a834f9 | ||
|
|
c0df8ac507 | ||
|
|
ab529991fd | ||
|
|
92705837ba | ||
|
|
609d5451f9 | ||
|
|
2bb2caeeed | ||
|
|
61db49bfb7 | ||
|
|
a75678d305 | ||
|
|
d418c91efa | ||
|
|
7f2da5a55d | ||
|
|
5e592ccdcb | ||
|
|
016f50fb1c | ||
|
|
fe21668a2b | ||
|
|
1aab86a317 | ||
|
|
243d23d4e2 | ||
|
|
2fe17473ae | ||
|
|
609cf8cfac | ||
|
|
678da30b09 | ||
|
|
d19412f4bb | ||
|
|
1665d6e537 | ||
|
|
fd210f2404 | ||
|
|
c33729fb44 | ||
|
|
7983bf3ee4 | ||
|
|
a09f60aea7 | ||
|
|
28b5893c21 |
9
.gitattributes
vendored
9
.gitattributes
vendored
@@ -1,3 +1,10 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
Moonlight/wwwroot/* linguist-vendored
|
||||
Moonlight/wwwroot/** linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/scripts.bundle.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/widgets.bundle.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/js/theme.js linguist-vendored
|
||||
Moonlight/wwwroot/assets/css/boxicons.min.css linguist-vendored
|
||||
Moonlight/wwwroot/assets/css/style.bundle.css linguist-vendored
|
||||
Moonlight/wwwroot/assets/plugins/** linguist-vendored
|
||||
Moonlight/wwwroot/assets/fonts/** linguist-vendored
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ModrinthApiHelper
|
||||
|
||||
var request = new RestRequest(url)
|
||||
{
|
||||
Timeout = 60 * 15
|
||||
Timeout = 300000
|
||||
};
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
11
Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs
Normal file
11
Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs
Normal 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; }
|
||||
}
|
||||
52
Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs
Normal file
52
Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
32
Moonlight/App/ApiClients/Telemetry/TelemetryException.cs
Normal file
32
Moonlight/App/ApiClients/Telemetry/TelemetryException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ public class WingsApiHelper
|
||||
|
||||
var request = new RestRequest(url)
|
||||
{
|
||||
Timeout = 60 * 15
|
||||
Timeout = 300000
|
||||
};
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
334
Moonlight/App/Configuration/ConfigV1.cs
Normal file
334
Moonlight/App/Configuration/ConfigV1.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
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("Auth")] public AuthData Auth { get; set; } = new();
|
||||
|
||||
[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("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();
|
||||
|
||||
[JsonProperty("Stripe")] public StripeData Stripe { get; set; } = new();
|
||||
}
|
||||
|
||||
public class StripeData
|
||||
{
|
||||
[JsonProperty("ApiKey")]
|
||||
[Description("Put here your stripe api key if you add subscriptions. Currently the only billing option is stripe which is enabled by default and cannot be turned off. This feature is still experimental")]
|
||||
public string ApiKey { get; set; } = "";
|
||||
}
|
||||
|
||||
public class AuthData
|
||||
{
|
||||
[JsonProperty("DenyLogin")]
|
||||
[Description("Prevent every new login")]
|
||||
public bool DenyLogin { get; set; } = false;
|
||||
|
||||
[JsonProperty("DenyRegister")]
|
||||
[Description("Prevent every new user to register")]
|
||||
public bool DenyRegister { get; set; } = false;
|
||||
}
|
||||
|
||||
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 SellPassData
|
||||
{
|
||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
||||
|
||||
[JsonProperty("Url")] public string Url { get; set; } = "https://not-implemented-yet";
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Database.Entities.Notification;
|
||||
using Moonlight.App.Database.Interceptors;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Database;
|
||||
@@ -27,10 +25,6 @@ public class DataContext : DbContext
|
||||
public DbSet<ServerVariable> ServerVariables { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<LoadingMessage> LoadingMessages { get; set; }
|
||||
public DbSet<AuditLogEntry> AuditLog { get; set; }
|
||||
public DbSet<ErrorLogEntry> ErrorLog { get; set; }
|
||||
public DbSet<SecurityLogEntry> SecurityLog { get; set; }
|
||||
|
||||
public DbSet<SharedDomain> SharedDomains { get; set; }
|
||||
public DbSet<Domain> Domains { get; set; }
|
||||
public DbSet<Revoke> Revokes { get; set; }
|
||||
@@ -46,20 +40,22 @@ public class DataContext : DbContext
|
||||
public DbSet<WebSpace> WebSpaces { get; set; }
|
||||
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
|
||||
public DbSet<IpBan> IpBans { get; set; }
|
||||
public DbSet<PermissionGroup> PermissionGroups { get; set; }
|
||||
public DbSet<SecurityLog> SecurityLogs { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
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,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
public class AuditLogEntry
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public AuditLogType Type { get; set; }
|
||||
public string JsonData { get; set; } = "";
|
||||
public bool System { get; set; }
|
||||
public string Ip { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
public class ErrorLogEntry
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Stacktrace { get; set; } = "";
|
||||
public bool System { get; set; }
|
||||
public string JsonData { get; set; } = "";
|
||||
public string Ip { get; set; } = "";
|
||||
public string Class { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
public class SecurityLogEntry
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool System { get; set; }
|
||||
public string Ip { get; set; } = "";
|
||||
public SecurityLogType Type { get; set; }
|
||||
public string JsonData { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
8
Moonlight/App/Database/Entities/PermissionGroup.cs
Normal file
8
Moonlight/App/Database/Entities/PermissionGroup.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class PermissionGroup
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public byte[] Permissions { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
8
Moonlight/App/Database/Entities/SecurityLog.cs
Normal file
8
Moonlight/App/Database/Entities/SecurityLog.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class SecurityLog
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Text { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class Subscription
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public Currency Currency { get; set; } = Currency.USD;
|
||||
public double Price { get; set; }
|
||||
public string StripeProductId { get; set; } = "";
|
||||
public string StripePriceId { get; set; } = "";
|
||||
public string LimitsJson { get; set; } = "";
|
||||
public int Duration { get; set; } = 30;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Moonlight.App.Models.Misc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
@@ -39,6 +40,8 @@ public class User
|
||||
public bool TotpEnabled { get; set; } = false;
|
||||
public string TotpSecret { get; set; } = "";
|
||||
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
|
||||
public byte[] Permissions { get; set; } = Array.Empty<byte>();
|
||||
public PermissionGroup? PermissionGroup { get; set; }
|
||||
|
||||
// Discord
|
||||
public ulong DiscordId { get; set; }
|
||||
@@ -51,6 +54,10 @@ public class User
|
||||
// Subscriptions
|
||||
|
||||
public Subscription? CurrentSubscription { get; set; } = null;
|
||||
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
|
||||
public int SubscriptionDuration { get; set; }
|
||||
public DateTime SubscriptionSince { get; set; } = DateTime.UtcNow;
|
||||
public DateTime SubscriptionExpires { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Ip logs
|
||||
public string RegisterIp { get; set; } = "";
|
||||
public string LastIp { get; set; } = "";
|
||||
}
|
||||
1088
Moonlight/App/Database/Migrations/20230703175432_AddedIpLogsForUser.Designer.cs
generated
Normal file
1088
Moonlight/App/Database/Migrations/20230703175432_AddedIpLogsForUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
1105
Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs
generated
Normal file
1105
Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedStripeIntegration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "SubscriptionExpires",
|
||||
table: "Users",
|
||||
type: "datetime(6)",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Currency",
|
||||
table: "Subscriptions",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Duration",
|
||||
table: "Subscriptions",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "Price",
|
||||
table: "Subscriptions",
|
||||
type: "double",
|
||||
nullable: false,
|
||||
defaultValue: 0.0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StripePriceId",
|
||||
table: "Subscriptions",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StripeProductId",
|
||||
table: "Subscriptions",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionExpires",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Currency",
|
||||
table: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Duration",
|
||||
table: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Price",
|
||||
table: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StripePriceId",
|
||||
table: "Subscriptions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StripeProductId",
|
||||
table: "Subscriptions");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SubscriptionDuration",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
1109
Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs
generated
Normal file
1109
Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPermissions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "Permissions",
|
||||
table: "Users",
|
||||
type: "longblob",
|
||||
nullable: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Permissions",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
1139
Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs
generated
Normal file
1139
Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPermissionGroup : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PermissionGroupId",
|
||||
table: "Users",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PermissionGroups",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Permissions = table.Column<byte[]>(type: "longblob", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PermissionGroups", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_PermissionGroupId",
|
||||
table: "Users",
|
||||
column: "PermissionGroupId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_PermissionGroups_PermissionGroupId",
|
||||
table: "Users",
|
||||
column: "PermissionGroupId",
|
||||
principalTable: "PermissionGroups",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_PermissionGroups_PermissionGroupId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PermissionGroups");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_PermissionGroupId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PermissionGroupId",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
1068
Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs
generated
Normal file
1068
Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemovedOldLogsAndAddedErrorLog : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AuditLog");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ErrorLog");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SecurityLog");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SecurityLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Text = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SecurityLogs", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SecurityLogs");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AuditLog",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
Type = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AuditLog", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ErrorLog",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Class = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Stacktrace = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
System = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ErrorLog", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SecurityLog",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
Type = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SecurityLog", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,95 +241,6 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("LoadingMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Ip")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("System")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AuditLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Class")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Ip")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Stacktrace")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("System")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ErrorLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Ip")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("System")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SecurityLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -475,6 +386,25 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("NotificationClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<byte[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PermissionGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -490,6 +420,24 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("Revokes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Text")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SecurityLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -663,10 +611,16 @@ namespace Moonlight.App.Database.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Currency")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Duration")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("LimitsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -675,6 +629,17 @@ namespace Moonlight.App.Database.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<double>("Price")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<string>("StripePriceId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StripeProductId")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Subscriptions");
|
||||
@@ -766,6 +731,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");
|
||||
@@ -777,9 +746,20 @@ namespace Moonlight.App.Database.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("PermissionGroupId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<byte[]>("Permissions")
|
||||
.IsRequired()
|
||||
.HasColumnType("longblob");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("RegisterIp")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ServerListLayoutJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -794,8 +774,8 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.Property<bool>("StreamerMode")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("SubscriptionDuration")
|
||||
.HasColumnType("int");
|
||||
b.Property<DateTime>("SubscriptionExpires")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("SubscriptionSince")
|
||||
.HasColumnType("datetime(6)");
|
||||
@@ -820,6 +800,8 @@ namespace Moonlight.App.Database.Migrations
|
||||
|
||||
b.HasIndex("CurrentSubscriptionId");
|
||||
|
||||
b.HasIndex("PermissionGroupId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
@@ -1024,7 +1006,13 @@ namespace Moonlight.App.Database.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("CurrentSubscriptionId");
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup")
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionGroupId");
|
||||
|
||||
b.Navigation("CurrentSubscription");
|
||||
|
||||
b.Navigation("PermissionGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||
|
||||
30
Moonlight/App/Extensions/JSRuntimeExtensions.cs
Normal file
30
Moonlight/App/Extensions/JSRuntimeExtensions.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Moonlight/App/Helpers/BitHelper.cs
Normal file
88
Moonlight/App/Helpers/BitHelper.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class BitHelper
|
||||
{
|
||||
public static bool ReadBit(byte[] byteArray, int bitIndex)
|
||||
{
|
||||
if (bitIndex < 0)
|
||||
throw new ArgumentOutOfRangeException("bitIndex");
|
||||
|
||||
int byteIndex = bitIndex / 8;
|
||||
if (byteIndex >= byteArray.Length)
|
||||
throw new ArgumentOutOfRangeException("bitIndex");
|
||||
|
||||
int bitNumber = bitIndex % 8;
|
||||
byte mask = (byte)(1 << bitNumber);
|
||||
|
||||
return (byteArray[byteIndex] & mask) != 0;
|
||||
}
|
||||
|
||||
public static byte[] WriteBit(byte[] byteArray, int bitIndex, bool value)
|
||||
{
|
||||
if (bitIndex < 0)
|
||||
throw new ArgumentOutOfRangeException("bitIndex");
|
||||
|
||||
int byteIndex = bitIndex / 8;
|
||||
byte[] resultArray;
|
||||
|
||||
if (byteIndex >= byteArray.Length)
|
||||
{
|
||||
// Create a new array with increased size and copy elements from old array
|
||||
resultArray = new byte[byteIndex + 1];
|
||||
Array.Copy(byteArray, resultArray, byteArray.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new array and copy elements from old array
|
||||
resultArray = new byte[byteArray.Length];
|
||||
Array.Copy(byteArray, resultArray, byteArray.Length);
|
||||
}
|
||||
|
||||
int bitNumber = bitIndex % 8;
|
||||
byte mask = (byte)(1 << bitNumber);
|
||||
|
||||
if (value)
|
||||
resultArray[byteIndex] |= mask; // Set the bit to 1
|
||||
else
|
||||
resultArray[byteIndex] &= (byte)~mask; // Set the bit to 0
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] OverwriteByteArrays(byte[] targetArray, byte[] overwriteArray)
|
||||
{
|
||||
int targetLength = targetArray.Length;
|
||||
int overwriteLength = overwriteArray.Length;
|
||||
|
||||
int maxLength = Math.Max(targetLength, overwriteLength);
|
||||
|
||||
byte[] resultArray = new byte[maxLength];
|
||||
|
||||
for (int i = 0; i < maxLength; i++)
|
||||
{
|
||||
byte targetByte = i < targetLength ? targetArray[i] : (byte)0;
|
||||
byte overwriteByte = i < overwriteLength ? overwriteArray[i] : (byte)0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
bool overwriteBit = (overwriteByte & (1 << j)) != 0;
|
||||
if (i < targetLength)
|
||||
{
|
||||
bool targetBit = (targetByte & (1 << j)) != 0;
|
||||
if (overwriteBit)
|
||||
{
|
||||
targetByte = targetBit ? (byte)(targetByte | (1 << j)) : (byte)(targetByte & ~(1 << j));
|
||||
}
|
||||
}
|
||||
else if (overwriteBit)
|
||||
{
|
||||
targetByte |= (byte)(1 << j);
|
||||
}
|
||||
}
|
||||
|
||||
resultArray[i] = targetByte;
|
||||
}
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
}
|
||||
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class BlurAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
@@ -66,15 +66,13 @@ 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");
|
||||
|
||||
|
||||
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Text;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public static class EggConverter
|
||||
{
|
||||
public static Image Convert(string json)
|
||||
{
|
||||
var result = new Image();
|
||||
|
||||
var data = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(json))
|
||||
).Build();
|
||||
|
||||
result.Allocations = 1;
|
||||
result.Description = data.GetValue<string>("description") ?? "";
|
||||
result.Uuid = Guid.NewGuid();
|
||||
result.Startup = data.GetValue<string>("startup") ?? "";
|
||||
result.Name = data.GetValue<string>("name") ?? "Ptero Egg";
|
||||
|
||||
foreach (var variable in data.GetSection("variables").GetChildren())
|
||||
{
|
||||
result.Variables.Add(new()
|
||||
{
|
||||
Key = variable.GetValue<string>("env_variable") ?? "",
|
||||
DefaultValue = variable.GetValue<string>("default_value") ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
var configData = data.GetSection("config");
|
||||
|
||||
result.ConfigFiles = configData.GetValue<string>("files") ?? "{}";
|
||||
|
||||
var dImagesData = JObject.Parse(json);
|
||||
var dImages = (JObject)dImagesData["docker_images"]!;
|
||||
|
||||
foreach (var dockerImage in dImages)
|
||||
{
|
||||
var di = new DockerImage()
|
||||
{
|
||||
Default = dockerImage.Key == dImages.Properties().Last().Name,
|
||||
Name = dockerImage.Value!.ToString()
|
||||
};
|
||||
|
||||
result.DockerImages.Add(di);
|
||||
}
|
||||
|
||||
var installSection = data.GetSection("scripts").GetSection("installation");
|
||||
|
||||
result.InstallEntrypoint = installSection.GetValue<string>("entrypoint") ?? "bash";
|
||||
result.InstallScript = installSection.GetValue<string>("script") ?? "";
|
||||
result.InstallDockerImage = installSection.GetValue<string>("container") ?? "";
|
||||
|
||||
var rawJson = configData.GetValue<string>("startup");
|
||||
|
||||
var startupData = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(rawJson!))
|
||||
).Build();
|
||||
|
||||
result.StartupDetection = startupData.GetValue<string>("done", "") ?? "";
|
||||
result.StopCommand = configData.GetValue<string>("stop") ?? "";
|
||||
|
||||
result.TagsJson = "[]";
|
||||
result.BackgroundImageUrl = "";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,46 +1,70 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.Files;
|
||||
using Serilog;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
// The private static instance of the config service, because we have no di here
|
||||
private static ConfigService ConfigService = new(new StorageService());
|
||||
|
||||
#region String method calls
|
||||
public static void Verbose(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Verbose("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
|
||||
public static void Info(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Information("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
|
||||
public static void Debug(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Debug("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
|
||||
public static void Error(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Error("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
|
||||
public static void Warn(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Warning("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
|
||||
public static void Fatal(string message, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Fatal("{Message}", message);
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(message);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -49,36 +73,54 @@ public static class Logger
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Verbose(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
|
||||
public static void Info(Exception exception, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Information(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
|
||||
public static void Debug(Exception exception, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Debug(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
|
||||
public static void Error(Exception exception, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Error(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
|
||||
public static void Warn(Exception exception, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Warning(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
|
||||
public static void Fatal(Exception exception, string channel = "default")
|
||||
{
|
||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||
.Fatal(exception, "");
|
||||
|
||||
if(channel == "security")
|
||||
LogSecurityInDb(exception);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -105,4 +147,25 @@ public static class Logger
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
|
||||
private static void LogSecurityInDb(Exception exception)
|
||||
{
|
||||
LogSecurityInDb(exception.ToStringDemystified());
|
||||
}
|
||||
private static void LogSecurityInDb(string text)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var dataContext = new DataContext(ConfigService);
|
||||
|
||||
dataContext.SecurityLogs.Add(new()
|
||||
{
|
||||
Text = text
|
||||
});
|
||||
|
||||
dataContext.SaveChanges();
|
||||
dataContext.Dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
51
Moonlight/App/Helpers/PropBinder.cs
Normal file
51
Moonlight/App/Helpers/PropBinder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -243,6 +243,7 @@ public class WingsConsole : IDisposable
|
||||
}
|
||||
}
|
||||
catch(JsonReaderException){}
|
||||
catch(JsonSerializationException){}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!Disconnecting)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class AvatarController : Controller
|
||||
|
||||
try
|
||||
{
|
||||
var url = GravatarController.GetImageUrl(user.Email, 100);
|
||||
var url = GravatarController.GetImageUrl(user.Email.ToLower(), 100);
|
||||
|
||||
using var client = new HttpClient();
|
||||
var res = await client.GetByteArrayAsync(url);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Stripe;
|
||||
using Stripe.Checkout;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/moonlight/billing")]
|
||||
public class BillingController : Controller
|
||||
{
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly BillingService BillingService;
|
||||
|
||||
public BillingController(
|
||||
IdentityService identityService,
|
||||
BillingService billingService)
|
||||
{
|
||||
IdentityService = identityService;
|
||||
BillingService = billingService;
|
||||
}
|
||||
|
||||
[HttpGet("cancel")]
|
||||
public async Task<ActionResult> Cancel()
|
||||
{
|
||||
var user = IdentityService.User;
|
||||
|
||||
if (user == null)
|
||||
return Redirect("/login");
|
||||
|
||||
return Redirect("/profile/subscriptions/close");
|
||||
}
|
||||
|
||||
[HttpGet("success")]
|
||||
public async Task<ActionResult> Success()
|
||||
{
|
||||
var user = IdentityService.User;
|
||||
|
||||
if (user == null)
|
||||
return Redirect("/login");
|
||||
|
||||
await BillingService.CompleteCheckout(user);
|
||||
|
||||
return Redirect("/profile/subscriptions/close");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RegisterController : Controller
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<TokenRegister>> Register()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
var user = IdentityService.User;
|
||||
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
|
||||
@@ -54,7 +54,7 @@ public class OAuth2Controller : Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUser = await IdentityService.Get();
|
||||
var currentUser = IdentityService.User;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
|
||||
@@ -27,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;
|
||||
}
|
||||
}
|
||||
|
||||
71
Moonlight/App/LogMigrator/SentryDiagnosticsLogger.cs
Normal file
71
Moonlight/App/LogMigrator/SentryDiagnosticsLogger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
@@ -10,4 +11,8 @@ public class SubscriptionDataModel
|
||||
|
||||
[Required(ErrorMessage = "You need to enter a description")]
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
public double Price { get; set; } = 0;
|
||||
public Currency Currency { get; set; } = Currency.USD;
|
||||
public int Duration { get; set; } = 30;
|
||||
}
|
||||
34
Moonlight/App/Models/Forms/UserEditDataModel.cs
Normal file
34
Moonlight/App/Models/Forms/UserEditDataModel.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
|
||||
public class UserEditDataModel
|
||||
{
|
||||
[Required]
|
||||
public string FirstName { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string LastName { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string Email { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string Address { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string City { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string State { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
public string Country { get; set; } = "";
|
||||
|
||||
public bool Admin { get; set; }
|
||||
public bool TotpEnabled { get; set; }
|
||||
public ulong DiscordId { get; set; }
|
||||
public PermissionGroup? PermissionGroup { get; set; }
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public enum AuditLogType
|
||||
{
|
||||
Login,
|
||||
Register,
|
||||
ChangePassword,
|
||||
ChangePowerState,
|
||||
CreateBackup,
|
||||
RestoreBackup,
|
||||
DeleteBackup,
|
||||
DownloadBackup,
|
||||
CreateServer,
|
||||
ReinstallServer,
|
||||
CancelSubscription,
|
||||
ApplySubscriptionCode,
|
||||
EnableTotp,
|
||||
DisableTotp,
|
||||
AddDomainRecord,
|
||||
UpdateDomainRecord,
|
||||
DeleteDomainRecord,
|
||||
PasswordReset,
|
||||
CleanupEnabled,
|
||||
CleanupDisabled,
|
||||
CleanupTriggered,
|
||||
PasswordChange,
|
||||
}
|
||||
7
Moonlight/App/Models/Misc/Currency.cs
Normal file
7
Moonlight/App/Models/Misc/Currency.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public enum Currency
|
||||
{
|
||||
USD = 1,
|
||||
EUR = 2
|
||||
}
|
||||
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Moonlight.App.Helpers.Files;
|
||||
|
||||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public class MailTemplate // This is just for the blazor table at /admin/system/mail
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public FileData File { get; set; }
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public enum SecurityLogType
|
||||
{
|
||||
ManipulatedJwt,
|
||||
PathTransversal,
|
||||
SftpBruteForce,
|
||||
LoginFail
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
10
Moonlight/App/Perms/Permission.cs
Normal file
10
Moonlight/App/Perms/Permission.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Moonlight.App.Perms;
|
||||
|
||||
public class Permission
|
||||
{
|
||||
public int Index { get; set; } = 0;
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
public static implicit operator int(Permission permission) => permission.Index;
|
||||
}
|
||||
11
Moonlight/App/Perms/PermissionRequired.cs
Normal file
11
Moonlight/App/Perms/PermissionRequired.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Moonlight.App.Perms;
|
||||
|
||||
public class PermissionRequired : Attribute
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public PermissionRequired(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
55
Moonlight/App/Perms/PermissionStorage.cs
Normal file
55
Moonlight/App/Perms/PermissionStorage.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Data;
|
||||
using Moonlight.App.Helpers;
|
||||
|
||||
namespace Moonlight.App.Perms;
|
||||
|
||||
public class PermissionStorage
|
||||
{
|
||||
public byte[] Data;
|
||||
public bool IsReadyOnly;
|
||||
|
||||
public PermissionStorage(byte[] data, bool isReadyOnly = false)
|
||||
{
|
||||
Data = data;
|
||||
IsReadyOnly = isReadyOnly;
|
||||
}
|
||||
|
||||
public bool this[Permission permission]
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return BitHelper.ReadBit(Data, permission.Index);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Verbose("Error reading permissions. (Can be intentional)");
|
||||
Logger.Verbose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (IsReadyOnly)
|
||||
throw new ReadOnlyException();
|
||||
|
||||
Data = BitHelper.WriteBit(Data, permission.Index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasAnyPermissions()
|
||||
{
|
||||
foreach (var permission in Permissions.GetAllPermissions())
|
||||
{
|
||||
if (this[permission])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
431
Moonlight/App/Perms/Permissions.cs
Normal file
431
Moonlight/App/Perms/Permissions.cs
Normal file
@@ -0,0 +1,431 @@
|
||||
namespace Moonlight.App.Perms;
|
||||
|
||||
public static class Permissions
|
||||
{
|
||||
public static Permission AdminDashboard = new()
|
||||
{
|
||||
Index = 0,
|
||||
Name = "Admin Dashboard",
|
||||
Description = "Access the main admin dashboard page"
|
||||
};
|
||||
|
||||
public static Permission AdminStatistics = new()
|
||||
{
|
||||
Index = 1,
|
||||
Name = "Admin Statistics",
|
||||
Description = "View statistical information about the moonlight instance"
|
||||
};
|
||||
|
||||
public static Permission AdminDomains = new()
|
||||
{
|
||||
Index = 4,
|
||||
Name = "Admin Domains",
|
||||
Description = "Manage domains in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewDomain = new()
|
||||
{
|
||||
Index = 5,
|
||||
Name = "Admin New Domain",
|
||||
Description = "Create a new domain in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSharedDomains = new()
|
||||
{
|
||||
Index = 6,
|
||||
Name = "Admin Shared Domains",
|
||||
Description = "Manage shared domains in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewSharedDomain = new()
|
||||
{
|
||||
Index = 7,
|
||||
Name = "Admin New Shared Domain",
|
||||
Description = "Create a new shared domain in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNodeDdos = new()
|
||||
{
|
||||
Index = 8,
|
||||
Name = "Admin Node DDoS",
|
||||
Description = "Manage DDoS protection for nodes in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNodeEdit = new()
|
||||
{
|
||||
Index = 9,
|
||||
Name = "Admin Node Edit",
|
||||
Description = "Edit node settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNodes = new()
|
||||
{
|
||||
Index = 10,
|
||||
Name = "Admin Node",
|
||||
Description = "Access the node management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewNode = new()
|
||||
{
|
||||
Index = 11,
|
||||
Name = "Admin New Node",
|
||||
Description = "Create a new node in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNodeSetup = new()
|
||||
{
|
||||
Index = 12,
|
||||
Name = "Admin Node Setup",
|
||||
Description = "Set up a node in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNodeView = new()
|
||||
{
|
||||
Index = 13,
|
||||
Name = "Admin Node View",
|
||||
Description = "View node details in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNotificationDebugging = new()
|
||||
{
|
||||
Index = 14,
|
||||
Name = "Admin Notification Debugging",
|
||||
Description = "Manage debugging notifications in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerCleanup = new()
|
||||
{
|
||||
Index = 15,
|
||||
Name = "Admin Server Cleanup",
|
||||
Description = "Perform server cleanup tasks in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerEdit = new()
|
||||
{
|
||||
Index = 16,
|
||||
Name = "Admin Server Edit",
|
||||
Description = "Edit server settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServers = new()
|
||||
{
|
||||
Index = 17,
|
||||
Name = "Admin Server",
|
||||
Description = "Access the server management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerManager = new()
|
||||
{
|
||||
Index = 18,
|
||||
Name = "Admin Server Manager",
|
||||
Description = "Manage servers in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewServer = new()
|
||||
{
|
||||
Index = 19,
|
||||
Name = "Admin New Server",
|
||||
Description = "Create a new server in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerImageEdit = new()
|
||||
{
|
||||
Index = 20,
|
||||
Name = "Admin Server Image Edit",
|
||||
Description = "Edit server image settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerImages = new()
|
||||
{
|
||||
Index = 21,
|
||||
Name = "Admin Server Images",
|
||||
Description = "Access the server image management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerImageNew = new()
|
||||
{
|
||||
Index = 22,
|
||||
Name = "Admin Server Image New",
|
||||
Description = "Create a new server image in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewAllocations = new()
|
||||
{
|
||||
Index = 23,
|
||||
Name = "Admin Server View Allocations",
|
||||
Description = "View server allocations in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewArchive = new()
|
||||
{
|
||||
Index = 24,
|
||||
Name = "Admin Server View Archive",
|
||||
Description = "View server archive in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewDebug = new()
|
||||
{
|
||||
Index = 25,
|
||||
Name = "Admin Server View Debug",
|
||||
Description = "View server debugging information in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewImage = new()
|
||||
{
|
||||
Index = 26,
|
||||
Name = "Admin Server View Image",
|
||||
Description = "View server image details in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewIndex = new()
|
||||
{
|
||||
Index = 27,
|
||||
Name = "Admin Server View",
|
||||
Description = "Access the server view page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewOverview = new()
|
||||
{
|
||||
Index = 28,
|
||||
Name = "Admin Server View Overview",
|
||||
Description = "View server overview in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminServerViewResources = new()
|
||||
{
|
||||
Index = 29,
|
||||
Name = "Admin Server View Resources",
|
||||
Description = "View server resources in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSubscriptionEdit = new()
|
||||
{
|
||||
Index = 30,
|
||||
Name = "Admin Subscription Edit",
|
||||
Description = "Edit subscription settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSubscriptions = new()
|
||||
{
|
||||
Index = 31,
|
||||
Name = "Admin Subscriptions",
|
||||
Description = "Access the subscription management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewSubscription = new()
|
||||
{
|
||||
Index = 32,
|
||||
Name = "Admin New Subscription",
|
||||
Description = "Create a new subscription in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSupport = new()
|
||||
{
|
||||
Index = 33,
|
||||
Name = "Admin Support",
|
||||
Description = "Access the support page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSupportView = new()
|
||||
{
|
||||
Index = 34,
|
||||
Name = "Admin Support View",
|
||||
Description = "View support details in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysConfiguration = new()
|
||||
{
|
||||
Index = 35,
|
||||
Name = "Admin system Configuration",
|
||||
Description = "Access system configuration settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysDiscordBot = new()
|
||||
{
|
||||
Index = 36,
|
||||
Name = "Admin system Discord Bot",
|
||||
Description = "Manage Discord bot settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSystem = new()
|
||||
{
|
||||
Index = 37,
|
||||
Name = "Admin system",
|
||||
Description = "Access the system management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysMail = new()
|
||||
{
|
||||
Index = 38,
|
||||
Name = "Admin system Mail",
|
||||
Description = "Manage mail settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSecurityMalware = new()
|
||||
{
|
||||
Index = 39,
|
||||
Name = "Admin security Malware",
|
||||
Description = "Manage malware settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysResources = new()
|
||||
{
|
||||
Index = 40,
|
||||
Name = "Admin system Resources",
|
||||
Description = "View system resources in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSecurity = new()
|
||||
{
|
||||
Index = 41,
|
||||
Name = "Admin Security",
|
||||
Description = "View security logs in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysSentry = new()
|
||||
{
|
||||
Index = 42,
|
||||
Name = "Admin system Sentry",
|
||||
Description = "Manage Sentry settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysNewsEdit = new()
|
||||
{
|
||||
Index = 43,
|
||||
Name = "Admin system News Edit",
|
||||
Description = "Edit system news in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysNews = new()
|
||||
{
|
||||
Index = 44,
|
||||
Name = "Admin system News",
|
||||
Description = "Access the system news management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSysNewsNew = new()
|
||||
{
|
||||
Index = 45,
|
||||
Name = "Admin system News New",
|
||||
Description = "Create new system news in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminUserEdit = new()
|
||||
{
|
||||
Index = 46,
|
||||
Name = "Admin User Edit",
|
||||
Description = "Edit user settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminUsers = new()
|
||||
{
|
||||
Index = 47,
|
||||
Name = "Admin Users",
|
||||
Description = "Access the user management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewUser = new()
|
||||
{
|
||||
Index = 48,
|
||||
Name = "Admin New User",
|
||||
Description = "Create a new user in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminUserSessions = new()
|
||||
{
|
||||
Index = 49,
|
||||
Name = "Admin User Sessions",
|
||||
Description = "View user sessions in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminUserView = new()
|
||||
{
|
||||
Index = 50,
|
||||
Name = "Admin User View",
|
||||
Description = "View user details in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminWebspaces = new()
|
||||
{
|
||||
Index = 51,
|
||||
Name = "Admin Webspaces",
|
||||
Description = "Access the webspaces management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminNewWebspace = new()
|
||||
{
|
||||
Index = 52,
|
||||
Name = "Admin New Webspace",
|
||||
Description = "Create a new webspace in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminWebspacesServerEdit = new()
|
||||
{
|
||||
Index = 53,
|
||||
Name = "Admin Webspaces Server Edit",
|
||||
Description = "Edit webspace server settings in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminWebspacesServers = new()
|
||||
{
|
||||
Index = 54,
|
||||
Name = "Admin Webspaces Servers",
|
||||
Description = "Access the webspace server management page in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminWebspacesServerNew = new()
|
||||
{
|
||||
Index = 55,
|
||||
Name = "Admin Webspaces Server New",
|
||||
Description = "Create a new webspace server in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSecurityIpBans = new()
|
||||
{
|
||||
Index = 56,
|
||||
Name = "Admin security ip bans",
|
||||
Description = "Manage ip bans in the admin area"
|
||||
};
|
||||
|
||||
public static Permission AdminSecurityPermissionGroups = new()
|
||||
{
|
||||
Index = 57,
|
||||
Name = "Admin security permission groups",
|
||||
Description = "View, add and delete permission groups"
|
||||
};
|
||||
|
||||
public static Permission AdminSecurityLogs = new()
|
||||
{
|
||||
Index = 58,
|
||||
Name = "Admin security logs",
|
||||
Description = "View the security logs"
|
||||
};
|
||||
|
||||
public static Permission? FromString(string name)
|
||||
{
|
||||
var type = typeof(Permissions);
|
||||
|
||||
var field = type
|
||||
.GetFields()
|
||||
.FirstOrDefault(x => x.FieldType == typeof(Permission) && x.Name == name);
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
var value = field.GetValue(null);
|
||||
return value as Permission;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Permission[] GetAllPermissions()
|
||||
{
|
||||
var type = typeof(Permissions);
|
||||
|
||||
return type
|
||||
.GetFields()
|
||||
.Where(x => x.FieldType == typeof(Permission))
|
||||
.Select(x => (x.GetValue(null) as Permission)!)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
namespace Moonlight.App.Repositories.LogEntries;
|
||||
|
||||
public class AuditLogEntryRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public AuditLogEntryRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public AuditLogEntry Add(AuditLogEntry entry)
|
||||
{
|
||||
var x = DataContext.AuditLog.Add(entry);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public DbSet<AuditLogEntry> Get()
|
||||
{
|
||||
return DataContext.AuditLog;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
namespace Moonlight.App.Repositories.LogEntries;
|
||||
|
||||
public class ErrorLogEntryRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public ErrorLogEntryRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public ErrorLogEntry Add(ErrorLogEntry errorLogEntry)
|
||||
{
|
||||
var x = DataContext.ErrorLog.Add(errorLogEntry);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public DbSet<ErrorLogEntry> Get()
|
||||
{
|
||||
return DataContext.ErrorLog;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
|
||||
namespace Moonlight.App.Repositories.LogEntries;
|
||||
|
||||
public class SecurityLogEntryRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public SecurityLogEntryRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public SecurityLogEntry Add(SecurityLogEntry securityLogEntry)
|
||||
{
|
||||
var x = DataContext.SecurityLog.Add(securityLogEntry);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public DbSet<SecurityLogEntry> Get()
|
||||
{
|
||||
return DataContext.SecurityLog;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,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);
|
||||
}
|
||||
@@ -63,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>();
|
||||
|
||||
@@ -22,19 +22,20 @@ 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);
|
||||
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
||||
Event.On<User>("user.rating", this, OnUserRated);
|
||||
Event.On<User>("billing.completed", this, OnBillingCompleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -42,6 +43,21 @@ public class DiscordNotificationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnBillingCompleted(User user)
|
||||
{
|
||||
await SendNotification("", builder =>
|
||||
{
|
||||
builder.Color = Color.Red;
|
||||
builder.Title = "New payment received";
|
||||
|
||||
builder.AddField("User", user.Email);
|
||||
builder.AddField("Firstname", user.FirstName);
|
||||
builder.AddField("Lastname", user.LastName);
|
||||
builder.AddField("Amount", user.CurrentSubscription!.Price);
|
||||
builder.AddField("Currency", user.CurrentSubscription!.Currency);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task OnUserRated(User user)
|
||||
{
|
||||
await SendNotification("", builder =>
|
||||
|
||||
62
Moonlight/App/Services/Background/TelemetryService.cs
Normal file
62
Moonlight/App/Services/Background/TelemetryService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Moonlight/App/Services/Background/TempMailService.cs
Normal file
37
Moonlight/App/Services/Background/TempMailService.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Net.Mail;
|
||||
using Moonlight.App.Helpers;
|
||||
|
||||
namespace Moonlight.App.Services.Background;
|
||||
|
||||
public class TempMailService
|
||||
{
|
||||
private string[] Domains = Array.Empty<string>();
|
||||
|
||||
public TempMailService()
|
||||
{
|
||||
Task.Run(Init);
|
||||
}
|
||||
|
||||
private async Task Init()
|
||||
{
|
||||
var client = new HttpClient();
|
||||
var text = await client.GetStringAsync("https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf");
|
||||
|
||||
Domains = text
|
||||
.Split("\n")
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
|
||||
Logger.Info($"Fetched {Domains.Length} temp mail domains");
|
||||
}
|
||||
|
||||
public Task<bool> IsTempMail(string mail)
|
||||
{
|
||||
var address = new MailAddress(mail);
|
||||
|
||||
if (Domains.Contains(address.Host))
|
||||
return Task.FromResult(true);
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
127
Moonlight/App/Services/BillingService.cs
Normal file
127
Moonlight/App/Services/BillingService.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Globalization;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.Mail;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Stripe.Checkout;
|
||||
using Subscription = Moonlight.App.Database.Entities.Subscription;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class BillingService
|
||||
{
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly SubscriptionService SubscriptionService;
|
||||
private readonly Repository<Subscription> SubscriptionRepository;
|
||||
private readonly SessionServerService SessionServerService;
|
||||
private readonly EventSystem Event;
|
||||
private readonly MailService MailService;
|
||||
|
||||
public BillingService(
|
||||
ConfigService configService,
|
||||
SubscriptionService subscriptionService,
|
||||
Repository<Subscription> subscriptionRepository,
|
||||
EventSystem eventSystem,
|
||||
SessionServerService sessionServerService,
|
||||
MailService mailService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
SubscriptionService = subscriptionService;
|
||||
SubscriptionRepository = subscriptionRepository;
|
||||
Event = eventSystem;
|
||||
SessionServerService = sessionServerService;
|
||||
MailService = mailService;
|
||||
}
|
||||
|
||||
public async Task<string> StartCheckout(User user, Subscription subscription)
|
||||
{
|
||||
var appUrl = ConfigService.Get().Moonlight.AppUrl;
|
||||
var controllerUrl = appUrl + "/api/moonlight/billing";
|
||||
|
||||
var options = new SessionCreateOptions()
|
||||
{
|
||||
LineItems = new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Price = subscription.StripePriceId,
|
||||
Quantity = 1
|
||||
}
|
||||
},
|
||||
Mode = "payment",
|
||||
SuccessUrl = controllerUrl + "/success",
|
||||
CancelUrl = controllerUrl + "/cancel",
|
||||
AutomaticTax = new SessionAutomaticTaxOptions()
|
||||
{
|
||||
Enabled = true
|
||||
},
|
||||
CustomerEmail = user.Email.ToLower(),
|
||||
Metadata = new()
|
||||
{
|
||||
{
|
||||
"productId",
|
||||
subscription.StripeProductId
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var service = new SessionService();
|
||||
|
||||
var session = await service.CreateAsync(options);
|
||||
|
||||
return session.Url;
|
||||
}
|
||||
public async Task CompleteCheckout(User user)
|
||||
{
|
||||
var sessionService = new SessionService();
|
||||
|
||||
var sessionsPerUser = await sessionService.ListAsync(new SessionListOptions()
|
||||
{
|
||||
CustomerDetails = new()
|
||||
{
|
||||
Email = user.Email
|
||||
}
|
||||
});
|
||||
|
||||
var latestCompletedSession = sessionsPerUser
|
||||
.Where(x => x.Status == "complete")
|
||||
.Where(x => x.PaymentStatus == "paid")
|
||||
.MaxBy(x => x.Created);
|
||||
|
||||
if (latestCompletedSession == null)
|
||||
throw new DisplayException("No completed session found");
|
||||
|
||||
var productId = latestCompletedSession.Metadata["productId"];
|
||||
|
||||
var subscription = SubscriptionRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.StripeProductId == productId);
|
||||
|
||||
if (subscription == null)
|
||||
throw new DisplayException("No subscription for this product found");
|
||||
|
||||
// if (await SubscriptionService.GetActiveSubscription(user) != null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
await SubscriptionService.SetActiveSubscription(user, subscription);
|
||||
|
||||
await MailService.SendMail(user, "checkoutComplete", values =>
|
||||
{
|
||||
values.Add("SubscriptionName", subscription.Name);
|
||||
values.Add("SubscriptionPrice", subscription.Price
|
||||
.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add("SubscriptionCurrency", subscription.Currency
|
||||
.ToString());
|
||||
values.Add("SubscriptionDuration", subscription.Duration
|
||||
.ToString(CultureInfo.InvariantCulture));
|
||||
});
|
||||
|
||||
await Event.Emit("billing.completed", user);
|
||||
|
||||
await SessionServerService.ReloadUserSessions(user);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Text;
|
||||
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 readonly string Path;
|
||||
private ConfigV1 Configuration;
|
||||
|
||||
public bool DebugMode { get; private set; } = false;
|
||||
public bool SqlDebugMode { get; private set; } = false;
|
||||
@@ -19,6 +19,11 @@ public class ConfigService : IConfiguration
|
||||
StorageService = storageService;
|
||||
StorageService.EnsureCreated();
|
||||
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ML_CONFIG_PATH")))
|
||||
Path = Environment.GetEnvironmentVariable("ML_CONFIG_PATH")!;
|
||||
else
|
||||
Path = PathBuilder.File("storage", "configs", "config.json");
|
||||
|
||||
Reload();
|
||||
|
||||
// Env vars
|
||||
@@ -41,33 +46,38 @@ public class ConfigService : IConfiguration
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
Configuration = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||
File.ReadAllText(
|
||||
PathBuilder.File("storage", "configs", "config.json")
|
||||
)
|
||||
)
|
||||
)).Build();
|
||||
if (!File.Exists(Path))
|
||||
{
|
||||
File.WriteAllText(Path, "{}");
|
||||
}
|
||||
|
||||
public IEnumerable<IConfigurationSection> GetChildren()
|
||||
{
|
||||
return Configuration.GetChildren();
|
||||
Configuration = JsonConvert.DeserializeObject<ConfigV1>(
|
||||
File.ReadAllText(Path)
|
||||
) ?? new ConfigV1();
|
||||
|
||||
File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||
}
|
||||
|
||||
public IChangeToken GetReloadToken()
|
||||
public void Save(ConfigV1 configV1)
|
||||
{
|
||||
return Configuration.GetReloadToken();
|
||||
Configuration = configV1;
|
||||
Save();
|
||||
}
|
||||
|
||||
public IConfigurationSection GetSection(string key)
|
||||
public void Save()
|
||||
{
|
||||
return Configuration.GetSection(key);
|
||||
if (!File.Exists(Path))
|
||||
{
|
||||
File.WriteAllText(Path, "{}");
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
public ConfigV1 Get()
|
||||
{
|
||||
get => Configuration[key];
|
||||
set => Configuration[key] = value;
|
||||
return Configuration;
|
||||
}
|
||||
}
|
||||
@@ -29,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;
|
||||
@@ -57,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)
|
||||
{
|
||||
|
||||
@@ -44,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;
|
||||
@@ -67,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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>"),
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Moonlight.App.Services;
|
||||
public class DomainService
|
||||
{
|
||||
private readonly DomainRepository DomainRepository;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly SharedDomainRepository SharedDomainRepository;
|
||||
private readonly CloudFlareClient Client;
|
||||
private readonly string AccountId;
|
||||
@@ -29,25 +30,29 @@ public class DomainService
|
||||
DomainRepository domainRepository,
|
||||
SharedDomainRepository sharedDomainRepository)
|
||||
{
|
||||
ConfigService = configService;
|
||||
DomainRepository = domainRepository;
|
||||
SharedDomainRepository = sharedDomainRepository;
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Task<Domain> Create(string domain, SharedDomain sharedDomain, User user)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
throw new DisplayException("This operation is disabled");
|
||||
|
||||
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
|
||||
throw new DisplayException("A domain with this name does already exist for this shared domain");
|
||||
|
||||
@@ -63,6 +68,9 @@ public class DomainService
|
||||
|
||||
public Task Delete(Domain domain)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
throw new DisplayException("This operation is disabled");
|
||||
|
||||
DomainRepository.Delete(domain);
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -71,6 +79,9 @@ public class DomainService
|
||||
public async Task<Zone[]>
|
||||
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
return Array.Empty<Zone>();
|
||||
|
||||
var domains = GetData(
|
||||
await Client.Zones.GetAsync(new()
|
||||
{
|
||||
@@ -93,6 +104,9 @@ public class DomainService
|
||||
|
||||
public async Task<DnsRecord[]> GetDnsRecords(Domain d)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
return Array.Empty<DnsRecord>();
|
||||
|
||||
var domain = EnsureData(d);
|
||||
|
||||
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
|
||||
@@ -146,7 +160,7 @@ public class DomainService
|
||||
Type = record.Type
|
||||
});
|
||||
}
|
||||
else if (record.Name.EndsWith(rname))
|
||||
else if (record.Name == rname)
|
||||
{
|
||||
result.Add(new()
|
||||
{
|
||||
@@ -165,6 +179,11 @@ public class DomainService
|
||||
}
|
||||
|
||||
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
throw new DisplayException("This operation is disabled");
|
||||
|
||||
try
|
||||
{
|
||||
var domain = EnsureData(d);
|
||||
|
||||
@@ -219,12 +238,24 @@ public class DomainService
|
||||
Name = name
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
throw new DisplayException("Invalid dns record values");
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
throw new DisplayException("Invalid dns record values");
|
||||
}
|
||||
|
||||
//TODO: AuditLog
|
||||
}
|
||||
|
||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
throw new DisplayException("This operation is disabled");
|
||||
|
||||
var domain = EnsureData(d);
|
||||
|
||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||
@@ -255,6 +286,9 @@ public class DomainService
|
||||
|
||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||
throw new DisplayException("This operation is disabled");
|
||||
|
||||
var domain = EnsureData(d);
|
||||
|
||||
GetData(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,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")))
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
18
Moonlight/App/Services/Interop/PopupService.cs
Normal file
18
Moonlight/App/Services/Interop/PopupService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Moonlight.App.Services.Interop;
|
||||
|
||||
public class PopupService
|
||||
{
|
||||
private readonly IJSRuntime JsRuntime;
|
||||
|
||||
public PopupService(IJSRuntime jsRuntime)
|
||||
{
|
||||
JsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task ShowCentered(string url, string title, int width = 500, int height = 500)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.popup.showCentered", url, title, width, height);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Repositories;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Moonlight.App.Services.Mail;
|
||||
@@ -14,41 +15,27 @@ public class MailService
|
||||
private readonly int Port;
|
||||
private readonly bool Ssl;
|
||||
|
||||
public MailService(ConfigService configService)
|
||||
private readonly Repository<User> UserRepository;
|
||||
|
||||
public MailService(
|
||||
ConfigService configService,
|
||||
Repository<User> userRepository)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
|
||||
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(
|
||||
User user,
|
||||
string name,
|
||||
Action<Dictionary<string, string>> values
|
||||
)
|
||||
public Task SendMailRaw(User user, string html)
|
||||
{
|
||||
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{name}.html")))
|
||||
{
|
||||
Logger.Warn($"Mail template '{name}' not found. Make sure to place one in the resources folder");
|
||||
throw new DisplayException("Mail template not found");
|
||||
}
|
||||
|
||||
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{name}.html"));
|
||||
|
||||
var val = new Dictionary<string, string>();
|
||||
values.Invoke(val);
|
||||
|
||||
val.Add("FirstName", user.FirstName);
|
||||
val.Add("LastName", user.LastName);
|
||||
|
||||
var parsed = ParseMail(rawHtml, val);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -62,24 +49,70 @@ public class MailService
|
||||
|
||||
var body = new BodyBuilder
|
||||
{
|
||||
HtmlBody = parsed
|
||||
HtmlBody = html
|
||||
};
|
||||
mailMessage.Body = body.ToMessageBody();
|
||||
|
||||
using (var smtpClient = new SmtpClient())
|
||||
{
|
||||
using var smtpClient = new SmtpClient();
|
||||
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
||||
await smtpClient.AuthenticateAsync(Email, Password);
|
||||
await smtpClient.SendAsync(mailMessage);
|
||||
await smtpClient.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error sending mail");
|
||||
Logger.Warn(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task SendMail(User user, string template, Action<Dictionary<string, string>> values)
|
||||
{
|
||||
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{template}.html")))
|
||||
{
|
||||
Logger.Warn($"Mail template '{template}' not found. Make sure to place one in the resources folder");
|
||||
throw new DisplayException("Mail template not found");
|
||||
}
|
||||
|
||||
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{template}.html"));
|
||||
|
||||
var val = new Dictionary<string, string>();
|
||||
values.Invoke(val);
|
||||
|
||||
val.Add("FirstName", user.FirstName);
|
||||
val.Add("LastName", user.LastName);
|
||||
|
||||
var parsed = ParseMail(rawHtml, val);
|
||||
|
||||
await SendMailRaw(user, parsed);
|
||||
}
|
||||
|
||||
public async Task SendEmailToAll(string template, Action<Dictionary<string, string>> values)
|
||||
{
|
||||
var users = UserRepository
|
||||
.Get()
|
||||
.ToArray();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
await SendMail(user, template, values);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendEmailToAllAdmins(string template, Action<Dictionary<string, string>> values)
|
||||
{
|
||||
var users = UserRepository
|
||||
.Get()
|
||||
.Where(x => x.Admin)
|
||||
.ToArray();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
await SendMail(user, template, values);
|
||||
}
|
||||
}
|
||||
|
||||
private string ParseMail(string html, Dictionary<string, string> values)
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Net;
|
||||
using Moonlight.App.Helpers;
|
||||
|
||||
namespace Moonlight.App.Services.Mail;
|
||||
|
||||
public class TrashMailDetectorService
|
||||
{
|
||||
private string[] Domains;
|
||||
|
||||
public TrashMailDetectorService()
|
||||
{
|
||||
Logger.Info("Fetching trash mail list from github repository");
|
||||
|
||||
using var wc = new WebClient();
|
||||
|
||||
var lines = wc
|
||||
.DownloadString("https://raw.githubusercontent.com/Endelon-Hosting/TrashMailDomainDetector/main/trashmail_domains.md")
|
||||
.Replace("\r\n", "\n")
|
||||
.Split(new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
Domains = GetDomains(lines).ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetDomains(string[] lines)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
if (line.Contains("."))
|
||||
{
|
||||
var domain = line.Remove(0, line.IndexOf(".", StringComparison.Ordinal) + 1).Trim();
|
||||
if (domain.Contains("."))
|
||||
{
|
||||
yield return domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTrashEmail(string mail)
|
||||
{
|
||||
return Domains.Contains(mail.Split('@')[1]);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -25,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);
|
||||
|
||||
@@ -55,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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -39,7 +39,7 @@ public class RatingService
|
||||
if (!Enabled)
|
||||
return false;
|
||||
|
||||
var user = await IdentityService.Get();
|
||||
var user = IdentityService.User;
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
@@ -62,7 +62,7 @@ public class RatingService
|
||||
|
||||
public async Task<bool> Rate(int rate)
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
var user = IdentityService.User;
|
||||
|
||||
// Double check states:
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
using JWT.Algorithms;
|
||||
using JWT.Builder;
|
||||
using JWT.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Perms;
|
||||
using Moonlight.App.Repositories;
|
||||
using UAParser;
|
||||
|
||||
@@ -12,16 +13,21 @@ namespace Moonlight.App.Services.Sessions;
|
||||
|
||||
public class IdentityService
|
||||
{
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly Repository<User> UserRepository;
|
||||
private readonly CookieService CookieService;
|
||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
||||
private readonly string Secret;
|
||||
|
||||
private User? UserCache;
|
||||
public User User { get; private set; }
|
||||
public string Ip { get; private set; } = "N/A";
|
||||
public string Device { get; private set; } = "N/A";
|
||||
public PermissionStorage Permissions { get; private set; }
|
||||
public PermissionStorage UserPermissions { get; private set; }
|
||||
public PermissionStorage GroupPermissions { get; private set; }
|
||||
|
||||
public IdentityService(
|
||||
CookieService cookieService,
|
||||
UserRepository userRepository,
|
||||
Repository<User> userRepository,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ConfigService configService)
|
||||
{
|
||||
@@ -30,18 +36,21 @@ public class IdentityService
|
||||
HttpContextAccessor = httpContextAccessor;
|
||||
|
||||
Secret = configService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("Security")
|
||||
.GetValue<string>("Token");
|
||||
.Get()
|
||||
.Moonlight.Security.Token;
|
||||
}
|
||||
|
||||
public async Task<User?> Get()
|
||||
public async Task Load()
|
||||
{
|
||||
await LoadIp();
|
||||
await LoadDevice();
|
||||
await LoadUser();
|
||||
}
|
||||
|
||||
private async Task LoadUser()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UserCache != null)
|
||||
return UserCache;
|
||||
|
||||
var token = "none";
|
||||
|
||||
// Load token via http context if available
|
||||
@@ -61,13 +70,13 @@ public class IdentityService
|
||||
|
||||
if (token == "none")
|
||||
{
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
return null;
|
||||
return;
|
||||
|
||||
var json = "";
|
||||
string json;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -78,18 +87,18 @@ public class IdentityService
|
||||
}
|
||||
catch (TokenExpiredException)
|
||||
{
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
catch (SignatureVerificationException)
|
||||
{
|
||||
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Error reading jwt");
|
||||
Logger.Error(e);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
// To make it easier to use the json data
|
||||
@@ -102,8 +111,9 @@ public class IdentityService
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
Logger.Warn($"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker");
|
||||
return null;
|
||||
Logger.Warn(
|
||||
$"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker");
|
||||
return;
|
||||
}
|
||||
|
||||
var iat = data.GetValue<long>("iat", -1);
|
||||
@@ -111,42 +121,54 @@ public class IdentityService
|
||||
if (iat == -1)
|
||||
{
|
||||
Logger.Debug("Legacy token found (without the time the token has been issued at)");
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
|
||||
|
||||
if (iatD < user.TokenValidTime)
|
||||
return null;
|
||||
return;
|
||||
|
||||
UserCache = user;
|
||||
return UserCache;
|
||||
User = user;
|
||||
|
||||
ConstructPermissions();
|
||||
|
||||
User.LastIp = Ip;
|
||||
UserRepository.Update(User);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Unexpected error while processing token");
|
||||
Logger.Error(e);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetIp()
|
||||
private Task LoadIp()
|
||||
{
|
||||
if (HttpContextAccessor.HttpContext == null)
|
||||
return "N/A";
|
||||
|
||||
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
|
||||
{
|
||||
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
|
||||
Ip = "N/A";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
if (HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
|
||||
{
|
||||
Ip = HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetDevice()
|
||||
Ip = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task LoadDevice()
|
||||
{
|
||||
if (HttpContextAccessor.HttpContext == null)
|
||||
return "N/A";
|
||||
{
|
||||
Device = "N/A";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -156,17 +178,86 @@ public class IdentityService
|
||||
{
|
||||
var version = userAgent.Remove(0, "Moonlight.App/".Length).Split(' ').FirstOrDefault();
|
||||
|
||||
return "Moonlight App " + version;
|
||||
Device = "Moonlight App " + version;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var uaParser = Parser.GetDefault();
|
||||
var info = uaParser.Parse(userAgent);
|
||||
|
||||
return $"{info.OS} - {info.Device}";
|
||||
Device = $"{info.OS} - {info.Device}";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return "UserAgent not present";
|
||||
Device = "UserAgent not present";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public Task SavePermissions()
|
||||
{
|
||||
if (User != null)
|
||||
{
|
||||
User.Permissions = UserPermissions.Data;
|
||||
UserRepository.Update(User);
|
||||
ConstructPermissions();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ConstructPermissions()
|
||||
{
|
||||
if (User == null)
|
||||
{
|
||||
UserPermissions = new(Array.Empty<byte>());
|
||||
GroupPermissions = new(Array.Empty<byte>(), true);
|
||||
Permissions = new(Array.Empty<byte>(), true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
.Include(x => x.PermissionGroup)
|
||||
.First(x => x.Id == User.Id);
|
||||
|
||||
UserPermissions = new PermissionStorage(user.Permissions);
|
||||
|
||||
if (user.PermissionGroup == null)
|
||||
GroupPermissions = new PermissionStorage(Array.Empty<byte>(), true);
|
||||
else
|
||||
GroupPermissions = new PermissionStorage(user.PermissionGroup.Permissions, true);
|
||||
|
||||
if (user.Admin)
|
||||
{
|
||||
Permissions = new PermissionStorage(Array.Empty<byte>());
|
||||
|
||||
foreach (var permission in Perms.Permissions.GetAllPermissions())
|
||||
{
|
||||
Permissions[permission] = true;
|
||||
}
|
||||
|
||||
Permissions.IsReadyOnly = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions = new(Array.Empty<byte>());
|
||||
|
||||
foreach (var permission in Perms.Permissions.GetAllPermissions())
|
||||
{
|
||||
Permissions[permission] = GroupPermissions[permission];
|
||||
}
|
||||
|
||||
foreach (var permission in Perms.Permissions.GetAllPermissions())
|
||||
{
|
||||
if (UserPermissions[permission])
|
||||
{
|
||||
Permissions[permission] = true;
|
||||
}
|
||||
}
|
||||
|
||||
Permissions.IsReadyOnly = true;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class IpBanService
|
||||
|
||||
public Task<bool> IsBanned()
|
||||
{
|
||||
var ip = IdentityService.GetIp();
|
||||
var ip = IdentityService.Ip;
|
||||
|
||||
return Task.FromResult(
|
||||
IpBanRepository
|
||||
|
||||
@@ -15,7 +15,7 @@ public class IpLocateService
|
||||
|
||||
public async Task<string> GetLocation()
|
||||
{
|
||||
var ip = IdentityService.GetIp();
|
||||
var ip = IdentityService.Ip;
|
||||
var location = "N/A";
|
||||
|
||||
if (ip != "N/A")
|
||||
|
||||
60
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
60
Moonlight/App/Services/Sessions/SessionClientService.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
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 string Ip { get; private set; } = "N/A";
|
||||
public string Device { get; private set; } = "N/A";
|
||||
|
||||
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 = IdentityService.User;
|
||||
Ip = IdentityService.Ip;
|
||||
Device = IdentityService.Device;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
64
Moonlight/App/Services/Sessions/SessionServerService.cs
Normal file
64
Moonlight/App/Services/Sessions/SessionServerService.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -17,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");
|
||||
@@ -36,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>>();
|
||||
@@ -46,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)
|
||||
{
|
||||
@@ -63,7 +61,7 @@ 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);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class SubscriptionAdminService
|
||||
{
|
||||
private readonly SubscriptionRepository SubscriptionRepository;
|
||||
private readonly OneTimeJwtService OneTimeJwtService;
|
||||
|
||||
public SubscriptionAdminService(OneTimeJwtService oneTimeJwtService, SubscriptionRepository subscriptionRepository)
|
||||
{
|
||||
OneTimeJwtService = oneTimeJwtService;
|
||||
SubscriptionRepository = subscriptionRepository;
|
||||
}
|
||||
|
||||
public Task<SubscriptionLimit[]> GetLimits(Subscription subscription)
|
||||
{
|
||||
return Task.FromResult(
|
||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
|
||||
?? Array.Empty<SubscriptionLimit>()
|
||||
);
|
||||
}
|
||||
|
||||
public Task SaveLimits(Subscription subscription, SubscriptionLimit[] limits)
|
||||
{
|
||||
subscription.LimitsJson = JsonConvert.SerializeObject(limits);
|
||||
SubscriptionRepository.Update(subscription);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<string> GenerateCode(Subscription subscription, int duration)
|
||||
{
|
||||
return Task.FromResult(
|
||||
OneTimeJwtService.Generate(data =>
|
||||
{
|
||||
data.Add("subscription", subscription.Id.ToString());
|
||||
data.Add("duration", duration.ToString());
|
||||
}, TimeSpan.FromDays(10324))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,151 +1,200 @@
|
||||
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;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Newtonsoft.Json;
|
||||
using Stripe;
|
||||
using File = System.IO.File;
|
||||
using Subscription = Moonlight.App.Database.Entities.Subscription;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class SubscriptionService
|
||||
{
|
||||
private readonly SubscriptionRepository SubscriptionRepository;
|
||||
private readonly OneTimeJwtService OneTimeJwtService;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly Repository<Subscription> SubscriptionRepository;
|
||||
private readonly Repository<User> UserRepository;
|
||||
|
||||
public SubscriptionService(
|
||||
SubscriptionRepository subscriptionRepository,
|
||||
OneTimeJwtService oneTimeJwtService,
|
||||
IdentityService identityService,
|
||||
UserRepository userRepository
|
||||
)
|
||||
Repository<Subscription> subscriptionRepository,
|
||||
Repository<User> userRepository)
|
||||
{
|
||||
SubscriptionRepository = subscriptionRepository;
|
||||
OneTimeJwtService = oneTimeJwtService;
|
||||
IdentityService = identityService;
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task<Subscription?> GetCurrent()
|
||||
public async Task<Subscription> Create(string name, string description, Currency currency, double price, int duration)
|
||||
{
|
||||
var user = await GetCurrentUser();
|
||||
|
||||
if (user == null || user.CurrentSubscription == null)
|
||||
return null;
|
||||
|
||||
var subscriptionEnd = user.SubscriptionSince.ToUniversalTime().AddDays(user.SubscriptionDuration);
|
||||
|
||||
if (subscriptionEnd > DateTime.UtcNow)
|
||||
var optionsProduct = new ProductCreateOptions
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
DefaultPriceData = new()
|
||||
{
|
||||
UnitAmount = (long)(price * 100),
|
||||
Currency = currency.ToString().ToLower()
|
||||
}
|
||||
};
|
||||
|
||||
var productService = new ProductService();
|
||||
var product = await productService.CreateAsync(optionsProduct);
|
||||
|
||||
var subscription = new Subscription()
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Currency = currency,
|
||||
Price = price,
|
||||
Duration = duration,
|
||||
LimitsJson = "[]",
|
||||
StripeProductId = product.Id,
|
||||
StripePriceId = product.DefaultPriceId
|
||||
};
|
||||
|
||||
return SubscriptionRepository.Add(subscription);
|
||||
}
|
||||
public async Task Update(Subscription subscription)
|
||||
{
|
||||
// Create the new price object
|
||||
|
||||
var optionsPrice = new PriceCreateOptions
|
||||
{
|
||||
UnitAmount = (long)(subscription.Price * 100),
|
||||
Currency = subscription.Currency.ToString().ToLower(),
|
||||
Product = subscription.StripeProductId
|
||||
};
|
||||
|
||||
var servicePrice = new PriceService();
|
||||
var price = await servicePrice.CreateAsync(optionsPrice);
|
||||
|
||||
// Update the product
|
||||
|
||||
var productService = new ProductService();
|
||||
var product = await productService.UpdateAsync(subscription.StripeProductId, new()
|
||||
{
|
||||
Name = subscription.Name,
|
||||
Description = subscription.Description,
|
||||
DefaultPrice = price.Id
|
||||
});
|
||||
|
||||
// Disable old price
|
||||
await servicePrice.UpdateAsync(subscription.StripePriceId, new()
|
||||
{
|
||||
Active = false
|
||||
});
|
||||
|
||||
// Update the model
|
||||
|
||||
subscription.StripeProductId = product.Id;
|
||||
subscription.StripePriceId = product.DefaultPriceId;
|
||||
|
||||
SubscriptionRepository.Update(subscription);
|
||||
}
|
||||
public async Task Delete(Subscription subscription)
|
||||
{
|
||||
var productService = new ProductService();
|
||||
await productService.DeleteAsync(subscription.StripeProductId);
|
||||
|
||||
SubscriptionRepository.Delete(subscription);
|
||||
}
|
||||
|
||||
public Task UpdateLimits(Subscription subscription, SubscriptionLimit[] limits)
|
||||
{
|
||||
subscription.LimitsJson = JsonConvert.SerializeObject(limits);
|
||||
SubscriptionRepository.Update(subscription);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task<SubscriptionLimit[]> GetLimits(Subscription subscription)
|
||||
{
|
||||
var limits =
|
||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson) ?? Array.Empty<SubscriptionLimit>();
|
||||
return Task.FromResult(limits);
|
||||
}
|
||||
|
||||
public async Task<Subscription?> GetActiveSubscription(User u)
|
||||
{
|
||||
var user = await EnsureData(u);
|
||||
|
||||
if (user.CurrentSubscription != null)
|
||||
{
|
||||
if (user.SubscriptionExpires < DateTime.UtcNow)
|
||||
{
|
||||
user.CurrentSubscription = null;
|
||||
UserRepository.Update(user);
|
||||
}
|
||||
}
|
||||
|
||||
return user.CurrentSubscription;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task ApplyCode(string code)
|
||||
public async Task CancelSubscription(User u)
|
||||
{
|
||||
var data = await OneTimeJwtService.Validate(code);
|
||||
|
||||
if (data == null)
|
||||
throw new DisplayException("Invalid or expired subscription code");
|
||||
|
||||
var id = int.Parse(data["subscription"]);
|
||||
var duration = int.Parse(data["duration"]);
|
||||
|
||||
var subscription = SubscriptionRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (subscription == null)
|
||||
throw new DisplayException("The subscription the code is associated with does not exist");
|
||||
|
||||
var user = await GetCurrentUser();
|
||||
|
||||
if (user == null)
|
||||
throw new DisplayException("Unable to determine current user");
|
||||
|
||||
user.CurrentSubscription = subscription;
|
||||
user.SubscriptionDuration = duration;
|
||||
user.SubscriptionSince = DateTime.UtcNow;
|
||||
|
||||
UserRepository.Update(user);
|
||||
|
||||
await OneTimeJwtService.Revoke(code);
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
if (await GetCurrent() != null)
|
||||
{
|
||||
var user = await GetCurrentUser();
|
||||
var user = await EnsureData(u);
|
||||
|
||||
user.CurrentSubscription = null;
|
||||
UserRepository.Update(user);
|
||||
}
|
||||
public async Task SetActiveSubscription(User u, Subscription subscription)
|
||||
{
|
||||
var user = await EnsureData(u);
|
||||
|
||||
user.SubscriptionSince = DateTime.UtcNow;
|
||||
user.SubscriptionExpires = DateTime.UtcNow.AddDays(subscription.Duration);
|
||||
user.CurrentSubscription = subscription;
|
||||
|
||||
UserRepository.Update(user);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SubscriptionLimit> GetLimit(string identifier) // Cache, optimize sql code
|
||||
{
|
||||
var subscription = await GetCurrent();
|
||||
var defaultLimits = await GetDefaultLimits();
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
};
|
||||
}
|
||||
|
||||
var subscriptionLimits =
|
||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
|
||||
?? Array.Empty<SubscriptionLimit>();
|
||||
|
||||
var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (foundLimit != null)
|
||||
return foundLimit;
|
||||
|
||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<User?> GetCurrentUser()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
if (user == null)
|
||||
return null;
|
||||
|
||||
var userWithData = UserRepository
|
||||
.Get()
|
||||
.Include(x => x.CurrentSubscription)
|
||||
.First(x => x.Id == user.Id);
|
||||
|
||||
return userWithData;
|
||||
}
|
||||
|
||||
private async Task<SubscriptionLimit[]> GetDefaultLimits() // Add cache and reload option
|
||||
public async Task<SubscriptionLimit[]> GetDefaultLimits()
|
||||
{
|
||||
var defaultSubscriptionJson = "[]";
|
||||
var path = PathBuilder.File("storage", "configs", "default_subscription.json");
|
||||
|
||||
if (File.Exists(PathBuilder.File("storage", "configs", "default_subscription.json")))
|
||||
if (File.Exists(path))
|
||||
{
|
||||
defaultSubscriptionJson =
|
||||
await File.ReadAllTextAsync(PathBuilder.File("storage", "configs", "default_subscription.json"));
|
||||
await File.ReadAllTextAsync(path);
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson) ?? Array.Empty<SubscriptionLimit>();
|
||||
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson)
|
||||
?? Array.Empty<SubscriptionLimit>();
|
||||
}
|
||||
public async Task<SubscriptionLimit> GetLimit(User u, string identifier)
|
||||
{
|
||||
var subscription = await GetActiveSubscription(u);
|
||||
var defaultLimits = await GetDefaultLimits();
|
||||
|
||||
if (subscription != null) // User has a active subscriptions
|
||||
{
|
||||
var subscriptionLimits = await GetLimits(subscription);
|
||||
|
||||
var subscriptionLimit = subscriptionLimits
|
||||
.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (subscriptionLimit != null) // Found subscription limit for the user's subscription
|
||||
return subscriptionLimit;
|
||||
} // If were are here, the user's subscription has no limit for this identifier, so we fallback to default
|
||||
|
||||
var defaultSubscriptionLimit = defaultLimits
|
||||
.FirstOrDefault(x => x.Identifier == identifier);
|
||||
|
||||
if (defaultSubscriptionLimit != null)
|
||||
return defaultSubscriptionLimit; // Default subscription limit found
|
||||
|
||||
return new() // No default subscription limit found
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
};
|
||||
}
|
||||
|
||||
private Task<User> EnsureData(User u)
|
||||
{
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
.Include(x => x.CurrentSubscription)
|
||||
.First(x => x.Id == u.Id);
|
||||
|
||||
return Task.FromResult(user);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class SupportChatAdminService : IDisposable
|
||||
|
||||
public async Task Start(User recipient)
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
User = IdentityService.User;
|
||||
Recipient = recipient;
|
||||
|
||||
if (User != null)
|
||||
|
||||
@@ -33,7 +33,7 @@ public class SupportChatClientService : IDisposable
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
User = IdentityService.User;
|
||||
|
||||
if (User != null)
|
||||
{
|
||||
|
||||
@@ -25,32 +25,30 @@ public class TotpService
|
||||
return Task.FromResult(codeserver == code);
|
||||
}
|
||||
|
||||
public async Task<bool> GetEnabled()
|
||||
public Task<bool> GetEnabled()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
return user!.TotpEnabled;
|
||||
return Task.FromResult(IdentityService.User.TotpEnabled);
|
||||
}
|
||||
|
||||
public async Task<string> GetSecret()
|
||||
public Task<string> GetSecret()
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
return user!.TotpSecret;
|
||||
return Task.FromResult(IdentityService.User.TotpSecret);
|
||||
}
|
||||
|
||||
public async Task GenerateSecret()
|
||||
public Task GenerateSecret()
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
var user = IdentityService.User;
|
||||
|
||||
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
|
||||
|
||||
UserRepository.Update(user);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Enable(string code)
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
var user = IdentityService.User;
|
||||
|
||||
if (!await Verify(user.TotpSecret, code))
|
||||
{
|
||||
@@ -61,9 +59,9 @@ public class TotpService
|
||||
UserRepository.Update(user);
|
||||
}
|
||||
|
||||
public async Task Disable()
|
||||
public Task Disable()
|
||||
{
|
||||
var user = (await IdentityService.Get())!;
|
||||
var user = IdentityService.User;
|
||||
|
||||
user.TotpEnabled = false;
|
||||
user.TotpSecret = "";
|
||||
@@ -71,5 +69,7 @@ public class TotpService
|
||||
UserRepository.Update(user);
|
||||
|
||||
//TODO: AuditLog
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.Background;
|
||||
using Moonlight.App.Services.Mail;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
||||
@@ -18,6 +19,8 @@ public class UserService
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly IpLocateService IpLocateService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly TempMailService TempMailService;
|
||||
|
||||
private readonly string JwtSecret;
|
||||
|
||||
@@ -28,23 +31,34 @@ public class UserService
|
||||
MailService mailService,
|
||||
IdentityService identityService,
|
||||
IpLocateService ipLocateService,
|
||||
DateTimeService dateTimeService)
|
||||
DateTimeService dateTimeService,
|
||||
TempMailService tempMailService)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
TotpService = totpService;
|
||||
ConfigService = configService;
|
||||
MailService = mailService;
|
||||
IdentityService = identityService;
|
||||
IpLocateService = ipLocateService;
|
||||
DateTimeService = dateTimeService;
|
||||
TempMailService = tempMailService;
|
||||
|
||||
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)
|
||||
{
|
||||
if (ConfigService.Get().Moonlight.Auth.DenyRegister)
|
||||
throw new DisplayException("This operation was disabled");
|
||||
|
||||
if (await TempMailService.IsTempMail(email))
|
||||
{
|
||||
Logger.Warn($"A user tried to use a blacklisted domain to register. Email: '{email}'", "security");
|
||||
throw new DisplayException("This email is blacklisted");
|
||||
}
|
||||
|
||||
// Check if the email is already taken
|
||||
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
|
||||
|
||||
@@ -73,7 +87,9 @@ public class UserService
|
||||
TotpEnabled = false,
|
||||
TotpSecret = "",
|
||||
UpdatedAt = DateTimeService.GetCurrent(),
|
||||
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
|
||||
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
|
||||
LastIp = IdentityService.Ip,
|
||||
RegisterIp = IdentityService.Ip
|
||||
});
|
||||
|
||||
await MailService.SendMail(user!, "register", values => {});
|
||||
@@ -107,6 +123,9 @@ public class UserService
|
||||
|
||||
public async Task<string> Login(string email, string password, string totpCode = "")
|
||||
{
|
||||
if (ConfigService.Get().Moonlight.Auth.DenyLogin)
|
||||
throw new DisplayException("This operation was disabled");
|
||||
|
||||
// First password check and check if totp is enabled
|
||||
var needTotp = await CheckTotp(email, password);
|
||||
|
||||
@@ -158,8 +177,8 @@ public class UserService
|
||||
|
||||
await MailService.SendMail(user!, "passwordChange", values =>
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Ip", IdentityService.Ip);
|
||||
values.Add("Device", IdentityService.Device);
|
||||
values.Add("Location", location);
|
||||
});
|
||||
|
||||
@@ -196,8 +215,8 @@ public class UserService
|
||||
{
|
||||
await MailService.SendMail(user!, "login", values =>
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Ip", IdentityService.Ip);
|
||||
values.Add("Device", IdentityService.Device);
|
||||
values.Add("Location", location);
|
||||
});
|
||||
}
|
||||
@@ -233,8 +252,8 @@ public class UserService
|
||||
|
||||
await MailService.SendMail(user, "passwordReset", values =>
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Ip", IdentityService.Ip);
|
||||
values.Add("Device", IdentityService.Device);
|
||||
values.Add("Location", location);
|
||||
values.Add("Password", newPassword);
|
||||
});
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
|
||||
<Router AppAssembly="@typeof(BlazorApp).Assembly">
|
||||
<Found Context="routeData">
|
||||
<CascadingValue TValue="Type" Name="TargetPageType" Value="routeData.PageType">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</CascadingValue>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(NotFoundLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
<NotFoundAlert />
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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" />
|
||||
@@ -46,10 +46,14 @@
|
||||
<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="Stripe.net" Version="41.23.0-beta.1" />
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
||||
</ItemGroup>
|
||||
@@ -73,6 +77,13 @@
|
||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\News\NewsEditor.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\News\Edit.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\AaPanels\Index.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Databases\Index.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\StateLogic\OnlyAdmin.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePassword.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePowerState.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryLogin.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryRegister.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -100,4 +111,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>
|
||||
|
||||
@@ -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,7 +96,6 @@
|
||||
</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>
|
||||
@@ -124,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>
|
||||
@@ -1,11 +1,11 @@
|
||||
using BlazorDownloadFile;
|
||||
using BlazorTable;
|
||||
using CurrieTechnologies.Razor.SweetAlert2;
|
||||
using HealthChecks.UI.Client;
|
||||
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;
|
||||
@@ -15,7 +15,6 @@ using Moonlight.App.Helpers.Wings;
|
||||
using Moonlight.App.LogMigrator;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Domains;
|
||||
using Moonlight.App.Repositories.LogEntries;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.Addon;
|
||||
@@ -29,8 +28,11 @@ 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.Sinks.SystemConsole.Themes;
|
||||
using Serilog.Events;
|
||||
using Stripe;
|
||||
using SubscriptionService = Moonlight.App.Services.SubscriptionService;
|
||||
|
||||
namespace Moonlight
|
||||
{
|
||||
@@ -40,14 +42,53 @@ namespace Moonlight
|
||||
{
|
||||
// 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
|
||||
@@ -57,10 +98,10 @@ namespace Moonlight
|
||||
.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");
|
||||
var databaseCheckupService = new DatabaseCheckupService(configService);
|
||||
@@ -71,8 +112,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();
|
||||
@@ -89,11 +130,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>();
|
||||
@@ -109,9 +166,6 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<NewsEntryRepository>();
|
||||
builder.Services.AddScoped<NodeAllocationRepository>();
|
||||
builder.Services.AddScoped<StatisticsRepository>();
|
||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||
builder.Services.AddScoped<SecurityLogEntryRepository>();
|
||||
builder.Services.AddScoped(typeof(Repository<>));
|
||||
|
||||
// Services
|
||||
@@ -120,7 +174,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>();
|
||||
@@ -152,13 +205,13 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<DynamicBackgroundService>();
|
||||
builder.Services.AddScoped<ServerAddonPluginService>();
|
||||
builder.Services.AddScoped<KeyListenerService>();
|
||||
|
||||
builder.Services.AddScoped<PopupService>();
|
||||
builder.Services.AddScoped<SubscriptionService>();
|
||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||
builder.Services.AddScoped<BillingService>();
|
||||
|
||||
// Loggers
|
||||
builder.Services.AddScoped<SessionClientService>();
|
||||
builder.Services.AddSingleton<SessionServerService>();
|
||||
builder.Services.AddScoped<MailService>();
|
||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||
|
||||
// Support chat
|
||||
builder.Services.AddSingleton<SupportChatServerService>();
|
||||
@@ -176,6 +229,7 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<DaemonApiHelper>();
|
||||
builder.Services.AddScoped<CloudPanelApiHelper>();
|
||||
builder.Services.AddScoped<ModrinthApiHelper>();
|
||||
builder.Services.AddScoped<TelemetryApiHelper>();
|
||||
|
||||
// Background services
|
||||
builder.Services.AddSingleton<DiscordBotService>();
|
||||
@@ -183,16 +237,21 @@ namespace Moonlight
|
||||
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||
builder.Services.AddSingleton<CleanupService>();
|
||||
builder.Services.AddSingleton<MalwareScanService>();
|
||||
builder.Services.AddSingleton<TelemetryService>();
|
||||
builder.Services.AddSingleton<TempMailService>();
|
||||
|
||||
// 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();
|
||||
|
||||
StripeConfiguration.ApiKey = configService
|
||||
.Get()
|
||||
.Moonlight.Stripe.ApiKey;
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@@ -203,6 +262,12 @@ namespace Moonlight
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
// Sentry
|
||||
if (shouldUseSentry)
|
||||
{
|
||||
app.UseSentryTracing();
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseWebSockets();
|
||||
@@ -222,6 +287,8 @@ namespace Moonlight
|
||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||
_ = app.Services.GetRequiredService<MalwareScanService>();
|
||||
_ = app.Services.GetRequiredService<TelemetryService>();
|
||||
_ = app.Services.GetRequiredService<TempMailService>();
|
||||
|
||||
_ = app.Services.GetRequiredService<MoonlightService>();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Moonlight": {
|
||||
"profiles":
|
||||
{
|
||||
"Default":
|
||||
{
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
@@ -10,41 +12,29 @@
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||
"dotnetRunMessages": true
|
||||
},
|
||||
"Sql Debug": {
|
||||
"Live DB":
|
||||
{
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ML_SQL_DEBUG": "true",
|
||||
"ML_DEBUG": "true"
|
||||
"ML_DEBUG": "true",
|
||||
"ML_CONFIG_PATH": "storage\\configs\\live_config.json"
|
||||
},
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||
"dotnetRunMessages": true
|
||||
},
|
||||
"Discord Bot": {
|
||||
"Dev DB 1":
|
||||
{
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ML_SQL_DEBUG": "true",
|
||||
"ML_DEBUG": "true"
|
||||
},
|
||||
"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"
|
||||
"ML_DEBUG": "true",
|
||||
"ML_CONFIG_PATH": "storage\\configs\\dev_1_config.json"
|
||||
},
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118"
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||
"dotnetRunMessages": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user