Merge pull request #226 from Moonlight-Panel/main

Update branch StripeIntegration with latest commits
This commit is contained in:
Marcel Baumgartner
2023-07-13 20:46:39 +02:00
committed by GitHub
34 changed files with 1370 additions and 315 deletions

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,27 @@
namespace Moonlight.App.Configuration;
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();
[JsonProperty("Moonlight")]
public MoonlightData Moonlight { get; set; } = new();
public class MoonlightData
{
[JsonProperty("AppUrl")] public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
[JsonProperty("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 DiscordBotData DiscordBotApi { get; set; } = new();
[JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new();
[JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new();
@@ -33,8 +41,7 @@ public class ConfigV1
[JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new();
[JsonProperty("DiscordNotifications")]
public DiscordNotificationsData DiscordNotifications { get; set; } = new();
[JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new();
[JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
@@ -51,20 +58,43 @@ public class ConfigV1
{
[JsonProperty("ApiKey")] 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")] public long Cpu { get; set; } = 90;
[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")] public long Memory { get; set; } = 8192;
[JsonProperty("Memory")]
[Description("The minumum amount of memory in megabytes avaliable before the cleanup starts")]
public long Memory { get; set; } = 8192;
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
[JsonProperty("Wait")]
[Description("The delay between every cleanup check in minutes")]
public long Wait { get; set; } = 15;
[JsonProperty("Uptime")] public long Uptime { get; set; } = 6;
[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")] public bool Enable { get; set; } = false;
[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")] public long MinUptime { get; set; } = 10;
[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
@@ -72,39 +102,77 @@ public class ConfigV1
[JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
[JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
[JsonProperty("Password")] public string Password { get; set; } = "secret";
[JsonProperty("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")] public bool Enable { get; set; } = false;
[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")] public string Token { get; set; } = "discord token here";
[JsonProperty("Token")]
[Description("Your discord bot token goes here")]
[Blur]
public string Token { get; set; } = "discord token here";
[JsonProperty("PowerActions")] public bool PowerActions { get; set; } = false;
[JsonProperty("SendCommands")] public bool SendCommands { get; set; } = false;
[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")] public bool Enable { get; set; } = false;
[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")] public string WebHook { get; set; } = "http://your-discord-webhook-url";
[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")] public bool Enable { get; set; } = false;
[JsonProperty("AccountId")] public string AccountId { get; set; } = "cloudflare acc id";
[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")] public string Email { get; set; } = "cloudflare@acc.email";
[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")] public string Key { get; set; } = "secret";
[JsonProperty("Key")]
[Description("Your cloudflare api key goes here")]
[Blur]
public string Key { get; set; } = "secret";
}
public class HtmlData
@@ -114,13 +182,21 @@ public class ConfigV1
public class HeadersData
{
[JsonProperty("Color")] public string Color { get; set; } = "#4b27e8";
[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")] public string Description { get; set; } = "the next generation hosting panel";
[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")] public string Keywords { get; set; } = "moonlight";
[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")] public string Title { get; set; } = "Moonlight - endelon.link";
[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
@@ -129,7 +205,9 @@ public class ConfigV1
[JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
[JsonProperty("Password")] public string Password { get; set; } = "secret";
[JsonProperty("Password")]
[Blur]
public string Password { get; set; } = "secret";
[JsonProperty("Port")] public int Port { get; set; } = 465;
@@ -149,9 +227,13 @@ public class ConfigV1
public class OAuth2Data
{
[JsonProperty("OverrideUrl")] public string OverrideUrl { get; set; } = "https://only-for-development.cases";
[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")] public bool EnableOverrideUrl { get; set; } = false;
[JsonProperty("EnableOverrideUrl")]
[Description("This enables the url override")]
public bool EnableOverrideUrl { get; set; } = false;
[JsonProperty("Providers")]
public OAuth2ProviderData[] Providers { get; set; } = Array.Empty<OAuth2ProviderData>();
@@ -163,41 +245,65 @@ public class ConfigV1
[JsonProperty("ClientId")] public string ClientId { get; set; }
[JsonProperty("ClientSecret")] public string ClientSecret { get; set; }
[JsonProperty("ClientSecret")]
[Blur]
public string ClientSecret { get; set; }
}
public class RatingData
{
[JsonProperty("Enabled")] public bool Enabled { get; set; } = false;
[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")] public string Url { get; set; } = "https://link-to-google-or-smth";
[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")] public int MinRating { get; set; } = 4;
[JsonProperty("MinRating")]
[Description("The minimum star count on the rating ranging from 1 to 5")]
public int MinRating { get; set; } = 4;
[JsonProperty("DaysSince")] public int DaysSince { get; set; } = 5;
[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")] public string Token { get; set; } = Guid.NewGuid().ToString();
[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")] public bool Enable { get; set; } = false;
[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")] public string SiteKey { get; set; } = "recaptcha site key here";
[JsonProperty("SiteKey")]
[Blur]
public string SiteKey { get; set; } = "recaptcha site key here";
[JsonProperty("SecretKey")] public string SecretKey { get; set; } = "recaptcha secret here";
[JsonProperty("SecretKey")]
[Blur]
public string SecretKey { get; set; } = "recaptcha secret here";
}
public class SentryData
{
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
[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")] public string Dsn { get; set; } = "http://your-sentry-url-here";
[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

View File

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

View 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;
}
}

View File

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

View File

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

View File

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

View 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; }
}

View File

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

View File

@@ -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);
}
}

View File

@@ -51,7 +51,27 @@ public class ConfigService
File.ReadAllText(path)
) ?? new ConfigV1();
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration));
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
}
public void Save(ConfigV1 configV1)
{
Configuration = configV1;
Save();
}
public void Save()
{
var path = PathBuilder.File("storage", "configs", "config.json");
if (!File.Exists(path))
{
File.WriteAllText(path, "{}");
}
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
Reload();
}
public ConfigV1 Get()

View File

@@ -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,6 +30,7 @@ public class DomainService
DomainRepository domainRepository,
SharedDomainRepository sharedDomainRepository)
{
ConfigService = configService;
DomainRepository = domainRepository;
SharedDomainRepository = sharedDomainRepository;
@@ -48,6 +50,9 @@ public class DomainService
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()
{
@@ -166,6 +180,9 @@ public class DomainService
public async Task AddDnsRecord(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}";
@@ -225,6 +242,9 @@ public class DomainService
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 +275,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(

View File

@@ -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,8 +15,14 @@ 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
.Get()
.Moonlight.Mail;
@@ -26,29 +33,9 @@ public class MailService
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,17 +49,15 @@ public class MailService
var body = new BodyBuilder
{
HtmlBody = parsed
HtmlBody = html
};
mailMessage.Body = body.ToMessageBody();
using (var smtpClient = new SmtpClient())
{
await smtpClient.ConnectAsync(Server, Port, Ssl);
await smtpClient.AuthenticateAsync(Email, Password);
await smtpClient.SendAsync(mailMessage);
await smtpClient.DisconnectAsync(true);
}
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)
{
@@ -80,6 +65,54 @@ public class MailService
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)

View File

@@ -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]);
}
}

View File

@@ -11,6 +11,8 @@ 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;
@@ -39,6 +41,8 @@ public class SessionClientService
public async Task Start()
{
User = await IdentityService.Get();
Ip = IdentityService.GetIp();
Device = IdentityService.GetDevice();
if (User != null) // Track users last visit
{

View File

@@ -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,14 +31,17 @@ 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
.Get()
@@ -44,6 +50,12 @@ public class UserService
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))
throw new DisplayException("This email is blacklisted");
// Check if the email is already taken
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
@@ -108,6 +120,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);