After the first try literally gave me a head ace, there is the second try with a better way of structuring it and not divinding so much core components in individual features. Still not done though

This commit is contained in:
Marcel Baumgartner
2024-01-25 21:40:23 +01:00
parent bb53f1c40a
commit 63b2b40227
228 changed files with 1143 additions and 1049 deletions

View File

@@ -0,0 +1,53 @@
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Event;
using Moonlight.Core.Event.Args;
namespace Moonlight.Core.Services.Background;
public class AutoMailSendService // This service is responsible for sending mails automatically
{
private readonly MailService MailService;
public AutoMailSendService(MailService mailService)
{
MailService = mailService;
Events.OnUserRegistered += OnUserRegistered;
Events.OnServiceOrdered += OnServiceOrdered;
Events.OnTransactionCreated += OnTransactionCreated;
}
private async void OnTransactionCreated(object? sender, TransactionCreatedEventArgs eventArgs)
{
await MailService.Send(
eventArgs.User,
"New transaction",
"transactionCreated",
eventArgs.Transaction,
eventArgs.User
);
}
private async void OnServiceOrdered(object? _, Service service)
{
await MailService.Send(
service.Owner,
"New product ordered",
"serviceOrdered",
service,
service.Product,
service.Owner
);
}
private async void OnUserRegistered(object? _, User user)
{
await MailService.Send(
user,
$"Welcome {user.Username}",
"welcome",
user
);
}
}

View File

@@ -0,0 +1,84 @@
using Moonlight.Core.Helpers;
namespace Moonlight.Core.Services;
public class BucketService
{
private readonly string BasePath;
public string[] Buckets => GetBuckets();
public BucketService()
{
// This is used to create the buckets folder in the persistent storage of helio
BasePath = PathBuilder.Dir("storage", "buckets");
Directory.CreateDirectory(BasePath);
}
public string[] GetBuckets()
{
return Directory
.GetDirectories(BasePath)
.Select(x =>
x.Replace(BasePath, "").TrimEnd('/')
)
.ToArray();
}
public Task EnsureBucket(string name) // To ensure a specific bucket has been created, call this function
{
Directory.CreateDirectory(PathBuilder.Dir(BasePath, name));
return Task.CompletedTask;
}
public async Task<string> Store(string bucket, Stream dataStream, string fileName)
{
await EnsureBucket(bucket); // Ensure the bucket actually exists
// Create a safe to file name to store the file
var extension = Path.GetExtension(fileName);
var finalFileName = Path.GetRandomFileName() + extension;
var finalFilePath = PathBuilder.File(BasePath, bucket, finalFileName);
// Copy the file from the remote stream to the bucket
var fs = File.Create(finalFilePath);
await dataStream.CopyToAsync(fs);
await fs.FlushAsync();
fs.Close();
// Return the generated file name to save it in the db or smth
return finalFileName;
}
public Task<Stream> Pull(string bucket, string file)
{
var filePath = PathBuilder.File(BasePath, bucket, file);
if (File.Exists(filePath))
{
var stream = File.Open(filePath, FileMode.Open);
return Task.FromResult<Stream>(stream);
}
else
throw new FileNotFoundException();
}
public Task Delete(string bucket, string file, bool ignoreNotFound = false)
{
var filePath = PathBuilder.File(BasePath, bucket, file);
if (File.Exists(filePath))
{
File.Delete(filePath);
return Task.CompletedTask;
}
// This section will only be reached if the file does not exist
if (!ignoreNotFound)
throw new FileNotFoundException();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,102 @@
using Moonlight.Core.Configuration;
using Moonlight.Core.Helpers;
using Newtonsoft.Json;
namespace Moonlight.Core.Services;
public class ConfigService
{
private readonly string Path = PathBuilder.File("storage", "config.json");
private ConfigV1 Data;
public ConfigService()
{
Reload();
}
public void Reload()
{
if(!File.Exists(Path))
File.WriteAllText(Path, "{}");
var text = File.ReadAllText(Path);
Data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
ApplyEnvironmentVariables("Moonlight", Data);
Save();
}
public void Save()
{
var text = JsonConvert.SerializeObject(Data, Formatting.Indented);
File.WriteAllText(Path, text);
}
public ConfigV1 Get()
{
return Data;
}
public string GetDiagnoseJson()
{
var text = File.ReadAllText(Path);
var data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
// Security token
data.Security.Token = "";
// Database
if (string.IsNullOrEmpty(data.Database.Password))
data.Database.Password = "WAS EMPTY";
else
data.Database.Password = "WAS NOT EMPTY";
// Mailserver
if (string.IsNullOrEmpty(data.MailServer.Password))
data.MailServer.Password = "WAS EMPTY";
else
data.MailServer.Password = "WAS NOT EMPTY";
return JsonConvert.SerializeObject(data, Formatting.Indented);
}
private void ApplyEnvironmentVariables(string prefix, object objectToLookAt)
{
foreach (var property in objectToLookAt.GetType().GetProperties())
{
var envName = $"{prefix}_{property.Name}";
if (property.PropertyType.Assembly == GetType().Assembly)
{
ApplyEnvironmentVariables(envName, property.GetValue(objectToLookAt)!);
}
else
{
if(!Environment.GetEnvironmentVariables().Contains(envName))
continue;
var envValue = Environment.GetEnvironmentVariable(envName)!;
if (property.PropertyType == typeof(string))
{
property.SetValue(objectToLookAt, envValue);
}
else if (property.PropertyType == typeof(int))
{
if(!int.TryParse(envValue, out int envIntValue))
continue;
property.SetValue(objectToLookAt, envIntValue);
}
else if (property.PropertyType == typeof(bool))
{
if(!bool.TryParse(envValue, out bool envBoolValue))
continue;
property.SetValue(objectToLookAt, envBoolValue);
}
}
}
}
}

View File

@@ -0,0 +1,188 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Exceptions;
using Moonlight.Core.Helpers;
using Moonlight.Core.Models.Abstractions;
using Moonlight.Core.Models.Enums;
using Moonlight.Core.Repositories;
using Moonlight.Core.Services.Utils;
using Moonlight.Features.StoreSystem.Entities;
using OtpNet;
namespace Moonlight.Core.Services;
// This service allows you to reauthenticate, login and force login
// It does also contain the permission system accessor for the current user
public class IdentityService
{
private readonly Repository<User> UserRepository;
private readonly JwtService JwtService;
private string Token;
public User? CurrentUserNullable { get; private set; }
public User CurrentUser => CurrentUserNullable!;
public bool IsSignedIn => CurrentUserNullable != null;
public FlagStorage Flags { get; private set; } = new("");
public PermissionStorage Permissions { get; private set; } = new(-1);
public Transaction[] Transactions => GetTransactions().Result; // TODO: make more efficient
public EventHandler OnAuthenticationStateChanged { get; set; }
public IdentityService(Repository<User> userRepository,
JwtService jwtService)
{
UserRepository = userRepository;
JwtService = jwtService;
}
// Transactions
public Task<Transaction[]> GetTransactions()
{
if (CurrentUserNullable == null)
return Task.FromResult(Array.Empty<Transaction>());
var user = UserRepository
.Get()
.Include(x => x.Transactions)
.First(x => x.Id == CurrentUserNullable.Id);
return Task.FromResult(user.Transactions.ToArray());
}
// Authentication
public async Task Authenticate() // Reauthenticate
{
// Save the last id (or -1 if not set) so we can track a change
var lastUserId = CurrentUserNullable == null ? -1 : CurrentUserNullable.Id;
// Reset
CurrentUserNullable = null;
await ValidateToken();
// Get current user id to compare against the last one
var currentUserId = CurrentUserNullable == null ? -1 : CurrentUserNullable.Id;
if (lastUserId != currentUserId) // State changed, lets notify all event listeners
OnAuthenticationStateChanged?.Invoke(this, null!);
}
private async Task ValidateToken() // Read and validate token
{
if (string.IsNullOrEmpty(Token))
return;
if (!await JwtService.Validate(Token))
return;
var data = await JwtService.Decode(Token);
if (!data.ContainsKey("userId"))
return;
var userId = int.Parse(data["userId"]);
var user = UserRepository
.Get()
.FirstOrDefault(x => x.Id == userId);
if (user == null)
return;
if (!data.ContainsKey("issuedAt"))
return;
var issuedAt = long.Parse(data["issuedAt"]);
var issuedAtDateTime = DateTimeOffset.FromUnixTimeSeconds(issuedAt).DateTime;
// If the valid time is newer then when the token was issued, the token is not longer valid
if (user.TokenValidTimestamp > issuedAtDateTime)
return;
CurrentUserNullable = user;
if (CurrentUserNullable == null) // If the current user is null, stop loading additional data
return;
Flags = new(CurrentUser.Flags);
Permissions = new(CurrentUser.Permissions);
}
public async Task<string> Login(string email, string password, string? code = null)
{
var user = UserRepository
.Get()
.FirstOrDefault(x => x.Email == email);
if (user == null)
throw new DisplayException("A user with these credential combination was not found");
if (!HashHelper.Verify(password, user.Password))
throw new DisplayException("A user with these credential combination was not found");
var flags = new FlagStorage(user.Flags); // Construct FlagStorage to check for 2fa
if (!flags[UserFlag.TotpEnabled]) // No 2fa found on this user so were done here
return await GenerateToken(user);
// If we reach this point, 2fa is enabled so we need to continue validating
if (string.IsNullOrEmpty(code)) // This will show an additional 2fa login field
throw new ArgumentNullException(nameof(code), "2FA code missing");
if (user.TotpKey == null) // Hopefully we will never fulfill this check ;)
throw new DisplayException("2FA key is missing. Please contact the support to fix your account");
// Calculate server side code
var totp = new Totp(Base32Encoding.ToBytes(user.TotpKey));
var codeServerSide = totp.ComputeTotp();
if (codeServerSide == code)
return await GenerateToken(user);
throw new DisplayException("Invalid 2fa code entered");
}
public async Task<string> GenerateToken(User user)
{
var token = await JwtService.Create(data =>
{
data.Add("userId", user.Id.ToString());
data.Add("issuedAt", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());
}, TimeSpan.FromDays(10));
return token;
}
public Task SaveFlags()
{
// Prevent saving flags for an empty user
if (!IsSignedIn)
return Task.CompletedTask;
// Save the new flag string
CurrentUser.Flags = Flags.RawFlagString;
UserRepository.Update(CurrentUser);
return Task.CompletedTask;
}
// Helpers and overloads
public async Task
Authenticate(HttpRequest request) // Overload for api controllers to authenticate a user like the normal panel
{
if (request.Cookies.ContainsKey("token"))
{
var token = request.Cookies["token"];
await Authenticate(token!);
}
}
public async Task Authenticate(string token) // Overload to set token and reauth
{
Token = token;
await Authenticate();
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.JSInterop;
using Moonlight.Core.Helpers;
namespace Moonlight.Core.Services.Interop;
public class AdBlockService
{
private readonly IJSRuntime JsRuntime;
public AdBlockService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async Task<bool> Detect()
{
try
{
return await JsRuntime.InvokeAsync<bool>("moonlight.utils.vendo"); // lat. vendo = advertisement xd
}
catch (Exception e)
{
Logger.Warn("An unexpected error occured while trying to detect possible ad blockers");
Logger.Warn(e);
return false;
}
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.JSInterop;
namespace Moonlight.Core.Services.Interop;
public class AlertService
{
private readonly IJSRuntime JsRuntime;
public AlertService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async Task Info(string title, string message)
{
await JsRuntime.InvokeVoidAsync("moonlight.alerts.info", title, message);
}
public async Task Success(string title, string message)
{
await JsRuntime.InvokeVoidAsync("moonlight.alerts.success", title, message);
}
public async Task Warning(string title, string message)
{
await JsRuntime.InvokeVoidAsync("moonlight.alerts.warning", title, message);
}
public async Task Error(string title, string message)
{
await JsRuntime.InvokeVoidAsync("moonlight.alerts.error", title, message);
}
public async Task<string> Text(string title, string message)
{
return await JsRuntime.InvokeAsync<string>("moonlight.alerts.text", title, message);
}
public async Task<bool> YesNo(string title, string yes, string no)
{
try
{
return await JsRuntime.InvokeAsync<bool>("moonlight.alerts.yesno", title, yes, no);
}
catch (Exception)
{
return false;
}
}
}

View File

@@ -0,0 +1,63 @@
using Microsoft.JSInterop;
namespace Moonlight.Core.Services.Interop;
public class CookieService
{
private readonly IJSRuntime JsRuntime;
private string Expires = "";
public CookieService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
ExpireDays = 300;
}
public async Task SetValue(string key, string value, int? days = null)
{
var curExp = (days != null) ? (days > 0 ? DateToUTC(days.Value) : "") : Expires;
await SetCookie($"{key}={value}; expires={curExp}; path=/");
}
public async Task<string> GetValue(string key, string def = "")
{
var cookieString = await GetCookie();
var cookieParts = cookieString.Split(";");
foreach (var cookiePart in cookieParts)
{
if(string.IsNullOrEmpty(cookiePart))
continue;
var cookieKeyValue = cookiePart.Split("=")
.Select(x => x.Trim()) // There may be spaces e.g. with the "AspNetCore.Culture" key
.ToArray();
if (cookieKeyValue.Length == 2)
{
if (cookieKeyValue[0] == key)
return cookieKeyValue[1];
}
}
return def;
}
private async Task SetCookie(string value)
{
await JsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{value}\"");
}
private async Task<string> GetCookie()
{
return await JsRuntime.InvokeAsync<string>("eval", $"document.cookie");
}
private int ExpireDays
{
set => Expires = DateToUTC(value);
}
private static string DateToUTC(int days) => DateTime.Now.AddDays(days).ToUniversalTime().ToString("R");
}

View File

@@ -0,0 +1,34 @@
using System.Text;
using Microsoft.JSInterop;
namespace Moonlight.Core.Services.Interop;
public class FileDownloadService
{
private readonly IJSRuntime JsRuntime;
public FileDownloadService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async Task DownloadStream(string fileName, Stream stream)
{
using var streamRef = new DotNetStreamReference(stream);
await JsRuntime.InvokeVoidAsync("moonlight.utils.download", fileName, streamRef);
}
public async Task DownloadBytes(string fileName, byte[] bytes)
{
var ms = new MemoryStream(bytes);
await DownloadStream(fileName, ms);
ms.Close();
await ms.DisposeAsync();
}
public async Task DownloadString(string fileName, string content) =>
await DownloadBytes(fileName, Encoding.UTF8.GetBytes(content));
}

View File

@@ -0,0 +1,37 @@
using Microsoft.JSInterop;
namespace Moonlight.Core.Services.Interop;
public class ModalService
{
private readonly IJSRuntime JsRuntime;
public ModalService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async Task Show(string id, bool focus = true) // Focus can be specified to fix issues with other components
{
try
{
await JsRuntime.InvokeVoidAsync("moonlight.modals.show", id, focus);
}
catch (Exception)
{
// ignored
}
}
public async Task Hide(string id)
{
try
{
await JsRuntime.InvokeVoidAsync("moonlight.modals.hide", id);
}
catch (Exception)
{
// Ignored
}
}
}

View File

@@ -0,0 +1,55 @@
using Microsoft.JSInterop;
namespace Moonlight.Core.Services.Interop;
public class ToastService
{
private readonly IJSRuntime JsRuntime;
public ToastService(IJSRuntime jsRuntime)
{
JsRuntime = jsRuntime;
}
public async Task Success(string title, string message, int timeout = 5000)
{
await JsRuntime.InvokeVoidAsync("moonlight.toasts.success", title, message, timeout);
}
public async Task Info(string title, string message, int timeout = 5000)
{
await JsRuntime.InvokeVoidAsync("moonlight.toasts.info", title, message, timeout);
}
public async Task Danger(string title, string message, int timeout = 5000)
{
await JsRuntime.InvokeVoidAsync("moonlight.toasts.danger", title, message, timeout);
}
public async Task Warning(string title, string message, int timeout = 5000)
{
await JsRuntime.InvokeVoidAsync("moonlight.toasts.warning", title, message, timeout);
}
// Overloads
public async Task Success(string message, int timeout = 5000)
{
await Success("", message, timeout);
}
public async Task Info(string message, int timeout = 5000)
{
await Info("", message, timeout);
}
public async Task Danger(string message, int timeout = 5000)
{
await Danger("", message, timeout);
}
public async Task Warning(string message, int timeout = 5000)
{
await Warning("", message, timeout);
}
}

View File

@@ -0,0 +1,103 @@
using MailKit.Net.Smtp;
using MimeKit;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Helpers;
namespace Moonlight.Core.Services;
public class MailService
{
private readonly ConfigService ConfigService;
private readonly string BasePath;
public MailService(ConfigService configService)
{
ConfigService = configService;
BasePath = PathBuilder.Dir("storage", "mail");
Directory.CreateDirectory(BasePath);
}
public async Task Send(User user, string title, string templateName, params object[] models)
{
var config = ConfigService.Get().MailServer;
try
{
// Build mail message
var message = new MimeMessage();
message.From.Add(new MailboxAddress(
config.SenderName,
config.Email
));
message.To.Add(new MailboxAddress(
$"{user.Username}",
user.Email
));
message.Subject = Formatter.ProcessTemplating(title, models);
var body = new BodyBuilder()
{
HtmlBody = await ParseTemplate(templateName, models)
};
message.Body = body.ToMessageBody();
// The actual sending will not be done in the mail thread to prevent long loading times
Task.Run(async () =>
{
using var smtpClient = new SmtpClient();
try
{
await smtpClient.ConnectAsync(config.Host, config.Port, config.UseSsl);
await smtpClient.AuthenticateAsync(config.Email, config.Password);
await smtpClient.SendAsync(message);
await smtpClient.DisconnectAsync(true);
}
catch (Exception e)
{
Logger.Warn("An unexpected error occured while connecting and transferring mail to mailserver");
Logger.Warn(e);
}
});
}
catch (FileNotFoundException)
{
// ignored as we log it anyways in the parse template function
}
catch (Exception e)
{
Logger.Warn("Unhandled error occured during sending mail:");
Logger.Warn(e);
}
}
private async Task<string> ParseTemplate(string templateName, params object[] models)
{
if (!File.Exists(PathBuilder.File(BasePath, templateName + ".html")))
{
Logger.Warn($"Mail template '{templateName}' is missing. Skipping sending mail");
throw new FileNotFoundException();
}
var text = await File.ReadAllTextAsync(
PathBuilder.File(BasePath, templateName + ".html")
);
// For details how the templating works, check out the explanation of the ProcessTemplating in the Formatter class
text = Formatter.ProcessTemplating(text, models);
return text;
}
// Helpers
public async Task Send(IEnumerable<User> users, string title, string templateName, params object[] models)
{
foreach (var user in users)
await Send(user, title, templateName, models);
}
}

View File

@@ -0,0 +1,145 @@
using System.Reflection;
using Moonlight.Core.Helpers;
using Moonlight.Core.Models.Abstractions.Services;
using Moonlight.Core.Plugins;
using Moonlight.Core.Plugins.Contexts;
namespace Moonlight.Core.Services;
public class PluginService
{
private readonly List<MoonlightPlugin> Plugins = new();
public async Task Load(WebApplicationBuilder webApplicationBuilder)
{
var path = PathBuilder.Dir("storage", "plugins");
Directory.CreateDirectory(path);
var files = FindFiles(path)
.Where(x => x.EndsWith(".dll"))
.ToArray();
foreach (var file in files)
{
try
{
var assembly = Assembly.LoadFile(PathBuilder.File(Directory.GetCurrentDirectory(), file));
int plugins = 0;
foreach (var type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(MoonlightPlugin)))
{
try
{
var plugin = (Activator.CreateInstance(type) as MoonlightPlugin)!;
// Create environment
plugin.Context = new PluginContext()
{
Services = webApplicationBuilder.Services,
WebApplicationBuilder = webApplicationBuilder
};
try
{
await plugin.Enable();
// After here we can treat the plugin as successfully loaded
plugins++;
Plugins.Add(plugin);
}
catch (Exception e)
{
Logger.Fatal($"Unhandled exception while enabling plugin '{type.Name}'");
Logger.Fatal(e);
}
}
catch (Exception e)
{
Logger.Fatal($"Failed to create plugin environment for '{type.Name}'");
Logger.Fatal(e);
}
}
}
if(plugins == 0) // If 0, we can assume that it was a library dll
Logger.Info($"Loaded {file} as a library");
else
Logger.Info($"Loaded {plugins} plugin(s) from {file}");
}
catch (Exception e)
{
Logger.Fatal($"Unable to load assembly from file '{file}'");
Logger.Fatal(e);
}
}
Logger.Info($"Loaded {Plugins.Count} plugin(s)");
}
public Task<MoonlightPlugin[]> GetLoadedPlugins() => Task.FromResult(Plugins.ToArray());
public async Task RunPreInit()
{
foreach (var plugin in Plugins)
{
Logger.Info($"Running pre init tasks for {plugin.GetType().Name}");
foreach (var preInitTask in plugin.Context.PreInitTasks)
await Task.Run(preInitTask);
}
}
public async Task RunPrePost(WebApplication webApplication)
{
foreach (var plugin in Plugins)
{
// Pass through the dependency injection
var scope = webApplication.Services.CreateScope();
plugin.Context.Provider = scope.ServiceProvider;
plugin.Context.Scope = scope;
plugin.Context.WebApplication = webApplication;
Logger.Info($"Running post init tasks for {plugin.GetType().Name}");
foreach (var postInitTask in plugin.Context.PostInitTasks)
await Task.Run(postInitTask);
}
}
public Task BuildUserServiceView(ServiceViewContext context)
{
foreach (var plugin in Plugins)
{
plugin.Context.BuildUserServiceView?.Invoke(context);
}
return Task.CompletedTask;
}
public Task BuildAdminServiceView(ServiceViewContext context)
{
foreach (var plugin in Plugins)
{
plugin.Context.BuildAdminServiceView?.Invoke(context);
}
return Task.CompletedTask;
}
private string[] FindFiles(string dir)
{
var result = new List<string>();
foreach (var file in Directory.GetFiles(dir))
result.Add(file);
foreach (var directory in Directory.GetDirectories(dir))
{
result.AddRange(FindFiles(directory));
}
return result.ToArray();
}
}

View File

@@ -0,0 +1,81 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Exceptions;
using Moonlight.Core.Repositories;
using Moonlight.Features.StoreSystem.Entities;
namespace Moonlight.Core.Services.ServiceManage;
public class ServiceAdminService
{
private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly ServiceDefinitionService ServiceDefinitionService;
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceDefinitionService serviceDefinitionService)
{
ServiceScopeFactory = serviceScopeFactory;
ServiceDefinitionService = serviceDefinitionService;
}
public async Task<Service> Create(User u, Product p, Action<Service>? modifyService = null)
{
var impl = ServiceDefinitionService.Get(p);
// Load models in new scope
using var scope = ServiceScopeFactory.CreateScope();
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var productRepo = scope.ServiceProvider.GetRequiredService<Repository<Product>>();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
var user = userRepo.Get().First(x => x.Id == u.Id);
var product = productRepo.Get().First(x => x.Id == p.Id);
// Create database model
var service = new Service()
{
Product = product,
Owner = user,
Suspended = false,
CreatedAt = DateTime.UtcNow
};
// Allow further modifications
if(modifyService != null)
modifyService.Invoke(service);
// Add new service in database
var finishedService = serviceRepo.Add(service);
// Call the action for the logic behind the service type
await impl.Actions.Create(scope.ServiceProvider, finishedService);
return finishedService;
}
public async Task Delete(Service s)
{
using var scope = ServiceScopeFactory.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
var serviceShareRepo = scope.ServiceProvider.GetRequiredService<Repository<ServiceShare>>();
var service = serviceRepo
.Get()
.Include(x => x.Shares)
.FirstOrDefault(x => x.Id == s.Id);
if (service == null)
throw new DisplayException("Service does not exist anymore");
var impl = ServiceDefinitionService.Get(service);
await impl.Actions.Delete(scope.ServiceProvider, service);
foreach (var share in service.Shares.ToArray())
{
serviceShareRepo.Delete(share);
}
serviceRepo.Delete(service);
}
}

View File

@@ -0,0 +1,56 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Database.Enums;
using Moonlight.Core.Models.Abstractions.Services;
using Moonlight.Core.Repositories;
using Moonlight.Features.StoreSystem.Entities;
namespace Moonlight.Core.Services.ServiceManage;
public class ServiceDefinitionService
{
private readonly Dictionary<ServiceType, ServiceDefinition> ServiceImplementations = new();
private readonly IServiceScopeFactory ServiceScopeFactory;
public ServiceDefinitionService(IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
}
public void Register<T>(ServiceType type) where T : ServiceDefinition
{
var impl = Activator.CreateInstance<T>() as ServiceDefinition;
if (impl == null)
throw new ArgumentException("The provided type is not an service implementation");
if (ServiceImplementations.ContainsKey(type))
throw new ArgumentException($"An implementation for {type} has already been registered");
ServiceImplementations.Add(type, impl);
}
public ServiceDefinition Get(Service s)
{
using var scope = ServiceScopeFactory.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
var service = serviceRepo
.Get()
.Include(x => x.Product)
.First(x => x.Id == s.Id);
return Get(service.Product);
}
public ServiceDefinition Get(Product p) => Get(p.Type);
public ServiceDefinition Get(ServiceType type)
{
if (!ServiceImplementations.ContainsKey(type))
throw new ArgumentException($"No service implementation found for {type}");
return ServiceImplementations[type];
}
}

View File

@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Models.Abstractions;
using Moonlight.Core.Models.Enums;
using Moonlight.Core.Repositories;
namespace Moonlight.Core.Services.ServiceManage;
public class ServiceManageService
{
private readonly IServiceScopeFactory ServiceScopeFactory;
public ServiceManageService(IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
}
public Task<bool> CheckAccess(Service s, User user)
{
var permissionStorage = new PermissionStorage(user.Permissions);
// Is admin?
if(permissionStorage[Permission.AdminServices])
return Task.FromResult(true);
using var scope = ServiceScopeFactory.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
var service = serviceRepo
.Get()
.Include(x => x.Owner)
.Include(x => x.Shares)
.ThenInclude(x => x.User)
.First(x => x.Id == s.Id);
// Is owner?
if(service.Owner.Id == user.Id)
return Task.FromResult(true);
// Is shared user
if(service.Shares.Any(x => x.User.Id == user.Id))
return Task.FromResult(true);
// No match
return Task.FromResult(false);
}
public Task<bool> NeedsRenewal(Service s)
{
// We fetch the service in a new scope wo ensure that we are not caching
using var scope = ServiceScopeFactory.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
var service = serviceRepo
.Get()
.First(x => x.Id == s.Id);
return Task.FromResult(DateTime.UtcNow > service.RenewAt);
}
}

View File

@@ -0,0 +1,114 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Exceptions;
using Moonlight.Core.Repositories;
namespace Moonlight.Core.Services.ServiceManage;
public class ServiceService // This service is used for managing services and create the connection to the actual logic behind a service type
{
private readonly IServiceProvider ServiceProvider;
private readonly Repository<Service> ServiceRepository;
private readonly Repository<User> UserRepository;
public ServiceAdminService Admin => ServiceProvider.GetRequiredService<ServiceAdminService>();
public ServiceDefinitionService Definition => ServiceProvider.GetRequiredService<ServiceDefinitionService>();
public ServiceManageService Manage => ServiceProvider.GetRequiredService<ServiceManageService>();
public ServiceService(IServiceProvider serviceProvider, Repository<Service> serviceRepository, Repository<User> userRepository)
{
ServiceProvider = serviceProvider;
ServiceRepository = serviceRepository;
UserRepository = userRepository;
}
public Task<Service[]> Get(User user)
{
var result = ServiceRepository
.Get()
.Include(x => x.Product)
.Where(x => x.Owner.Id == user.Id)
.ToArray();
return Task.FromResult(result);
}
public Task<Service[]> GetShared(User user)
{
var result = ServiceRepository
.Get()
.Include(x => x.Product)
.Include(x => x.Owner)
.Where(x => x.Shares.Any(y => y.User.Id == user.Id))
.ToArray();
return Task.FromResult(result);
}
public Task AddSharedUser(Service s, string username)
{
var userToAdd = UserRepository
.Get()
.FirstOrDefault(x => x.Username == username);
if (userToAdd == null)
throw new DisplayException("No user found with this username");
var service = ServiceRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.Shares)
.ThenInclude(x => x.User)
.First(x => x.Id == s.Id);
if (service.Owner.Id == userToAdd.Id)
throw new DisplayException("The owner cannot be added as a shared user");
if (service.Shares.Any(x => x.User.Id == userToAdd.Id))
throw new DisplayException("The user has already access to this service");
service.Shares.Add(new ()
{
User = userToAdd
});
ServiceRepository.Update(service);
return Task.CompletedTask;
}
public Task<User[]> GetSharedUsers(Service s)
{
var service = ServiceRepository
.Get()
.Include(x => x.Shares)
.ThenInclude(x => x.User)
.First(x => x.Id == s.Id);
var result = service.Shares
.Select(x => x.User)
.ToArray();
return Task.FromResult(result);
}
public Task RemoveSharedUser(Service s, User user)
{
var service = ServiceRepository
.Get()
.Include(x => x.Shares)
.ThenInclude(x => x.User)
.First(x => x.Id == s.Id);
var shareToRemove = service.Shares.FirstOrDefault(x => x.User.Id == user.Id);
if (shareToRemove == null)
throw new DisplayException("This user does not have access to this service");
service.Shares.Remove(shareToRemove);
ServiceRepository.Update(service);
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,38 @@
using Moonlight.Core.Models.Abstractions;
namespace Moonlight.Core.Services;
public class SessionService
{
private readonly List<Session> AllSessions = new();
public Session[] Sessions => GetSessions();
public Task Register(Session session)
{
lock (AllSessions)
{
AllSessions.Add(session);
}
return Task.CompletedTask;
}
public Task Unregister(Session session)
{
lock (AllSessions)
{
AllSessions.Remove(session);
}
return Task.CompletedTask;
}
public Session[] GetSessions()
{
lock (AllSessions)
{
return AllSessions.ToArray();
}
}
}

View File

@@ -0,0 +1,81 @@
using System.IO.Compression;
using Moonlight.Core.Event;
using Moonlight.Core.Extensions;
using Moonlight.Core.Helpers;
namespace Moonlight.Core.Services.Sys;
public class MoonlightService // This service can be used to perform strictly panel specific actions
{
private readonly ConfigService ConfigService;
private readonly IServiceProvider ServiceProvider;
public WebApplication Application { get; set; } // Do NOT modify using a plugin
public string LogPath { get; set; } // Do NOT modify using a plugin
public MoonlightThemeService Theme => ServiceProvider.GetRequiredService<MoonlightThemeService>();
public MoonlightService(ConfigService configService, IServiceProvider serviceProvider)
{
ConfigService = configService;
ServiceProvider = serviceProvider;
}
public async Task Restart()
{
Logger.Info("Restarting moonlight");
// Notify all users that this instance will restart
await Events.OnMoonlightRestart.InvokeAsync();
await Task.Delay(TimeSpan.FromSeconds(3));
await Application.StopAsync();
}
public async Task<byte[]> GenerateDiagnoseReport()
{
var scope = ServiceProvider.CreateScope();
// Prepare zip file
var memoryStream = new MemoryStream();
var zip = new ZipArchive(memoryStream, ZipArchiveMode.Create, true);
// Add current log
// We need to open the file this way because we need to specify the access and share mode directly
// in order to read from a file which is currently written to
var fs = File.Open(LogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sr = new StreamReader(fs);
var log = await sr.ReadToEndAsync();
sr.Close();
fs.Close();
await zip.AddFromText("log.txt", log);
// TODO: Add node settings here
// Add config
var config = ConfigService.GetDiagnoseJson();
await zip.AddFromText("config.json", config);
// Make a list of plugins
var pluginService = scope.ServiceProvider.GetRequiredService<PluginService>();
var plugins = await pluginService.GetLoadedPlugins();
var pluginList = "Installed plugins:\n";
foreach (var plugin in plugins)
{
var assembly = plugin.GetType().Assembly;
pluginList += $"{assembly.FullName} ({assembly.Location})\n";
}
await zip.AddFromText("pluginList.txt", pluginList);
// Add more information here
// Finalize file
zip.Dispose();
memoryStream.Close();
var data = memoryStream.ToArray();
return data;
}
}

View File

@@ -0,0 +1,51 @@
using Mappy.Net;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Models.Abstractions;
using Moonlight.Core.Repositories;
namespace Moonlight.Core.Services.Sys;
public class MoonlightThemeService
{
private readonly IServiceProvider ServiceProvider;
private readonly ConfigService ConfigService;
public MoonlightThemeService(IServiceProvider serviceProvider, ConfigService configService)
{
ServiceProvider = serviceProvider;
ConfigService = configService;
}
public Task<ApplicationTheme[]> GetInstalled()
{
using var scope = ServiceProvider.CreateScope();
var themeRepo = scope.ServiceProvider.GetRequiredService<Repository<Theme>>();
var themes = new List<ApplicationTheme>();
themes.AddRange(themeRepo
.Get()
.ToArray()
.Select(x => Mapper.Map<ApplicationTheme>(x)));
if (ConfigService.Get().Theme.EnableDefault)
{
themes.Insert(0, new()
{
Id = 0,
Name = "Moonlight Default",
Author = "MasuOwO",
Enabled = true,
CssUrl = "/css/theme.css",
DonateUrl = "https://ko-fi.com/masuowo"
});
}
return Task.FromResult(themes.ToArray());
}
public async Task<ApplicationTheme[]> GetEnabled() =>
(await GetInstalled())
.Where(x => x.Enabled)
.ToArray();
}

View File

@@ -0,0 +1,124 @@
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Event;
using Moonlight.Core.Exceptions;
using Moonlight.Core.Extensions;
using Moonlight.Core.Helpers;
using Moonlight.Core.Models.Abstractions;
using Moonlight.Core.Models.Enums;
using Moonlight.Core.Models.Templates;
using Moonlight.Core.Repositories;
using Moonlight.Core.Services.Utils;
using OtpNet;
namespace Moonlight.Core.Services.Users;
public class UserAuthService
{
private readonly Repository<User> UserRepository;
private readonly JwtService JwtService;
private readonly ConfigService ConfigService;
private readonly MailService MailService;
public UserAuthService(
Repository<User> userRepository,
JwtService jwtService,
ConfigService configService,
MailService mailService)
{
UserRepository = userRepository;
JwtService = jwtService;
ConfigService = configService;
MailService = mailService;
}
public async Task<User> Register(string username, string email, string password)
{
// Event though we have form validation i want to
// ensure that at least these basic formatting things are done
email = email.ToLower().Trim();
username = username.ToLower().Trim();
// Prevent duplication or username and/or email
if (UserRepository.Get().Any(x => x.Email == email))
throw new DisplayException("A user with that email does already exist");
if (UserRepository.Get().Any(x => x.Username == username))
throw new DisplayException("A user with that username does already exist");
var user = new User()
{
Username = username,
Email = email,
Password = HashHelper.HashToString(password)
};
var result = UserRepository.Add(user);
await Events.OnUserRegistered.InvokeAsync(result);
return result;
}
public async Task ChangePassword(User user, string newPassword)
{
user.Password = HashHelper.HashToString(newPassword);
user.TokenValidTimestamp = DateTime.UtcNow;
UserRepository.Update(user);
await Events.OnUserPasswordChanged.InvokeAsync(user);
}
public Task SeedTotp(User user)
{
var key = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));
user.TotpKey = key;
UserRepository.Update(user);
return Task.CompletedTask;
}
public async Task SetTotp(User user, bool state)
{
// Access to flags without identity service
var flags = new FlagStorage(user.Flags);
flags[UserFlag.TotpEnabled] = state;
user.Flags = flags.RawFlagString;
if (!state)
user.TotpKey = null;
UserRepository.Update(user);
await Events.OnUserTotpSet.InvokeAsync(user);
}
// Mails
public async Task SendVerification(User user)
{
var jwt = await JwtService.Create(data => { data.Add("mailToVerify", user.Email); }, TimeSpan.FromMinutes(10));
await MailService.Send(user, "Verify your account", "verifyMail", user, new MailVerify()
{
Url = ConfigService.Get().AppUrl + "/api/auth/verify?token=" + jwt
});
}
public async Task SendResetPassword(string email)
{
var user = UserRepository
.Get()
.FirstOrDefault(x => x.Email == email);
if (user == null)
throw new DisplayException("An account with that email was not found");
var jwt = await JwtService.Create(data => { data.Add("accountToReset", user.Id.ToString()); });
await MailService.Send(user, "Password reset for your account", "passwordReset", user, new ResetPassword()
{
Url = ConfigService.Get().AppUrl + "/api/auth/reset?token=" + jwt
});
}
}

View File

@@ -0,0 +1,170 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Database.Entities.Store;
using Moonlight.Core.Repositories;
using Moonlight.Core.Services.ServiceManage;
using Moonlight.Features.Community.Entities;
using Moonlight.Features.Community.Services;
using Moonlight.Features.StoreSystem.Entities;
using Moonlight.Features.Ticketing.Entities;
namespace Moonlight.Core.Services.Users;
public class UserDeleteService
{
private readonly Repository<Service> ServiceRepository;
private readonly Repository<ServiceShare> ServiceShareRepository;
private readonly Repository<Post> PostRepository;
private readonly Repository<User> UserRepository;
private readonly Repository<Transaction> TransactionRepository;
private readonly Repository<CouponUse> CouponUseRepository;
private readonly Repository<GiftCodeUse> GiftCodeUseRepository;
private readonly Repository<Ticket> TicketRepository;
private readonly Repository<TicketMessage> TicketMessageRepository;
private readonly ServiceService ServiceService;
private readonly PostService PostService;
public UserDeleteService(
Repository<Service> serviceRepository,
ServiceService serviceService,
PostService postService,
Repository<Post> postRepository,
Repository<User> userRepository,
Repository<GiftCodeUse> giftCodeUseRepository,
Repository<CouponUse> couponUseRepository,
Repository<Transaction> transactionRepository,
Repository<Ticket> ticketRepository,
Repository<TicketMessage> ticketMessageRepository,
Repository<ServiceShare> serviceShareRepository)
{
ServiceRepository = serviceRepository;
ServiceService = serviceService;
PostService = postService;
PostRepository = postRepository;
UserRepository = userRepository;
GiftCodeUseRepository = giftCodeUseRepository;
CouponUseRepository = couponUseRepository;
TransactionRepository = transactionRepository;
TicketRepository = ticketRepository;
TicketMessageRepository = ticketMessageRepository;
ServiceShareRepository = serviceShareRepository;
}
public async Task Perform(User user)
{
// Community
// - Posts
foreach (var post in PostRepository.Get().ToArray())
{
await PostService.Delete(post);
}
// - Comments
var posts = PostRepository
.Get()
.Where(x => x.Comments.Any(y => y.Author.Id == user.Id))
.ToArray();
foreach (var post in posts)
{
var comments = PostRepository
.Get()
.Include(x => x.Comments)
.ThenInclude(x => x.Author)
.First(x => x.Id == post.Id)
.Comments
.Where(x => x.Author.Id == user.Id)
.ToArray();
foreach (var comment in comments)
await PostService.DeleteComment(post, comment);
}
// Services
foreach (var service in ServiceRepository.Get().Where(x => x.Owner.Id == user.Id).ToArray())
{
await ServiceService.Admin.Delete(service);
}
// Service shares
var shares = ServiceShareRepository
.Get()
.Where(x => x.User.Id == user.Id)
.ToArray();
foreach (var share in shares)
{
ServiceShareRepository.Delete(share);
}
// Transactions - Coupons - Gift codes
var userWithDetails = UserRepository
.Get()
.Include(x => x.Transactions)
.Include(x => x.CouponUses)
.Include(x => x.GiftCodeUses)
.First(x => x.Id == user.Id);
var giftCodeUses = userWithDetails.GiftCodeUses.ToArray();
var couponUses = userWithDetails.CouponUses.ToArray();
var transactions = userWithDetails.Transactions.ToArray();
userWithDetails.GiftCodeUses.Clear();
userWithDetails.CouponUses.Clear();
userWithDetails.Transactions.Clear();
UserRepository.Update(userWithDetails);
foreach (var giftCodeUse in giftCodeUses)
GiftCodeUseRepository.Delete(giftCodeUse);
foreach (var couponUse in couponUses)
CouponUseRepository.Delete(couponUse);
foreach (var transaction in transactions)
TransactionRepository.Delete(transaction);
// Tickets and ticket messages
// First we need to fetch every message this user has sent and delete it as admin accounts can have messages
// in tickets they dont own
var messagesFromUser = TicketMessageRepository
.Get()
.Where(x => x.Sender.Id == user.Id)
.ToArray();
foreach (var message in messagesFromUser)
{
TicketMessageRepository.Delete(message);
}
// Now we can only delete the tickets the user actually owns
var tickets = TicketRepository
.Get()
.Include(x => x.Messages)
.Where(x => x.Creator.Id == user.Id)
.ToArray();
foreach (var ticket in tickets)
{
var messages = ticket.Messages.ToArray(); // Cache message models
ticket.Messages.Clear();
TicketRepository.Update(ticket);
foreach (var ticketMessage in messages)
{
TicketMessageRepository.Delete(ticketMessage);
}
TicketRepository.Delete(ticket);
}
// User
// We need to use this in order to entity framework not crashing because of the previous deleted data
var userToDelete = UserRepository.Get().First(x => x.Id == user.Id);
UserRepository.Delete(userToDelete);
}
}

View File

@@ -0,0 +1,40 @@
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Repositories;
namespace Moonlight.Core.Services.Users;
public class UserDetailsService
{
private readonly BucketService BucketService;
private readonly Repository<User> UserRepository;
public UserDetailsService(BucketService bucketService, Repository<User> userRepository)
{
BucketService = bucketService;
UserRepository = userRepository;
}
public async Task UpdateAvatar(User user, Stream stream, string fileName)
{
if (user.Avatar != null)
{
await BucketService.Delete("avatars", user.Avatar, true);
}
var file = await BucketService.Store("avatars", stream, fileName);
user.Avatar = file;
UserRepository.Update(user);
}
public async Task UpdateAvatar(User user) // Overload to reset avatar
{
if (user.Avatar != null)
{
await BucketService.Delete("avatars", user.Avatar, true);
}
user.Avatar = null;
UserRepository.Update(user);
}
}

View File

@@ -0,0 +1,45 @@
using Moonlight.Core.Database.Entities;
using Moonlight.Core.Exceptions;
using Moonlight.Core.Repositories;
namespace Moonlight.Core.Services.Users;
public class UserService
{
private readonly Repository<User> UserRepository;
private readonly IServiceProvider ServiceProvider;
public UserAuthService Auth => ServiceProvider.GetRequiredService<UserAuthService>();
public UserDetailsService Details => ServiceProvider.GetRequiredService<UserDetailsService>();
public UserDeleteService Delete => ServiceProvider.GetRequiredService<UserDeleteService>();
public UserService(
Repository<User> userRepository,
IServiceProvider serviceProvider)
{
UserRepository = userRepository;
ServiceProvider = serviceProvider;
}
public Task Update(User user, string username, string email)
{
// Event though we have form validation i want to
// ensure that at least these basic formatting things are done
email = email.ToLower().Trim();
username = username.ToLower().Trim();
// Prevent duplication or username and/or email
if (UserRepository.Get().Any(x => x.Email == email && x.Id != user.Id))
throw new DisplayException("A user with that email does already exist");
if (UserRepository.Get().Any(x => x.Username == username && x.Id != user.Id))
throw new DisplayException("A user with that username does already exist");
user.Username = username;
user.Email = email;
UserRepository.Update(user);
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,34 @@
using Moonlight.Core.Helpers;
namespace Moonlight.Core.Services.Utils;
public class ConnectionService
{
private readonly IHttpContextAccessor ContextAccessor;
private readonly ConfigService ConfigService;
public ConnectionService(IHttpContextAccessor contextAccessor, ConfigService configService)
{
ContextAccessor = contextAccessor;
ConfigService = configService;
}
public Task<string> GetIpAddress()
{
if (ContextAccessor.HttpContext == null)
return Task.FromResult("N/A (Missing http context)");
var request = ContextAccessor.HttpContext.Request;
if (request.Headers.ContainsKey("X-Real-IP"))
{
if(ConfigService.Get().Security.EnableReverseProxyMode)
return Task.FromResult(request.Headers["X-Real-IP"].ToString());
Logger.Warn($"Detected an ip mask attempt by using a fake X-Real-IP header. Fake IP: {request.Headers["X-Real-IP"]}. Real IP: {ContextAccessor.HttpContext.Connection.RemoteIpAddress}");
return Task.FromResult(ContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "N/A (Remote IP missing)");
}
return Task.FromResult(ContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "N/A (Remote IP missing)");
}
}

View File

@@ -0,0 +1,75 @@
using JWT.Algorithms;
using JWT.Builder;
using Moonlight.Core.Helpers;
using Newtonsoft.Json;
namespace Moonlight.Core.Services.Utils;
public class JwtService
{
private readonly ConfigService ConfigService;
private readonly TimeSpan DefaultDuration = TimeSpan.FromDays(365 * 10);
public JwtService(ConfigService configService)
{
ConfigService = configService;
}
public Task<string> Create(Action<Dictionary<string, string>> data, TimeSpan? validDuration = null)
{
var builder = new JwtBuilder()
.WithSecret(ConfigService.Get().Security.Token)
.IssuedAt(DateTime.UtcNow)
.ExpirationTime(DateTime.UtcNow.Add(validDuration ?? DefaultDuration))
.WithAlgorithm(new HMACSHA512Algorithm());
var dataDic = new Dictionary<string, string>();
data.Invoke(dataDic);
foreach (var entry in dataDic)
builder = builder.AddClaim(entry.Key, entry.Value);
var jwt = builder.Encode();
return Task.FromResult(jwt);
}
public Task<bool> Validate(string token)
{
try
{
_ = new JwtBuilder()
.WithSecret(ConfigService.Get().Security.Token)
.WithAlgorithm(new HMACSHA512Algorithm())
.MustVerifySignature()
.Decode(token);
return Task.FromResult(true);
}
catch (Exception e)
{
Logger.Warn(e.Message);
return Task.FromResult(false);
}
}
public Task<Dictionary<string, string>> Decode(string token)
{
try
{
var json = new JwtBuilder()
.WithSecret(ConfigService.Get().Security.Token)
.WithAlgorithm(new HMACSHA512Algorithm())
.MustVerifySignature()
.Decode(token);
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
return Task.FromResult(data)!;
}
catch (Exception)
{
return Task.FromResult<Dictionary<string, string>>(null!);
}
}
}