Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25b47d8b6c | ||
|
|
85f5b8a7da | ||
|
|
ab9333f99a | ||
|
|
d60f8fc905 | ||
|
|
fe1f4412d8 | ||
|
|
f191533410 | ||
|
|
a894707536 | ||
|
|
d2ccc84286 | ||
|
|
f2ec43f2d2 | ||
|
|
7feccc8d9f | ||
|
|
a8bd1193ce | ||
|
|
366d1a9205 | ||
|
|
df9ed95c6b | ||
|
|
23a211362e | ||
|
|
cf91d44902 | ||
|
|
35633e21a9 | ||
|
|
ce8b8f6798 | ||
|
|
c28c80ba25 | ||
|
|
da17b1df93 | ||
|
|
f9f5865ef9 | ||
|
|
389ded9b77 | ||
|
|
faebaa59dd | ||
|
|
6b7dc2ad05 | ||
|
|
e356c9d0c8 | ||
|
|
efed2a6a5c | ||
|
|
6f138c2c51 | ||
|
|
6c43e6a533 | ||
|
|
0379afd831 | ||
|
|
b8bfdb7729 | ||
|
|
72f60ec97c | ||
|
|
1b40250750 | ||
|
|
cdf2988cb6 | ||
|
|
76415b4a0a | ||
|
|
52d00baf2b | ||
|
|
cd41db510e | ||
|
|
1afd4e8b92 | ||
|
|
ef37088c7a | ||
|
|
e71495533b | ||
|
|
e2a6d70f6a | ||
|
|
e95853b09c | ||
|
|
0537ca115e | ||
| 0fde9a5005 | |||
|
|
74541d7f87 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1,3 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
|
Moonlight/wwwroot/* linguist-vendored
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
namespace Moonlight.App.ApiClients.Modrinth;
|
namespace Moonlight.App.ApiClients.Modrinth;
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public class User
|
|||||||
|
|
||||||
public string Country { get; set; } = "";
|
public string Country { get; set; } = "";
|
||||||
|
|
||||||
|
public string ServerListLayoutJson { get; set; } = "";
|
||||||
|
|
||||||
// States
|
// States
|
||||||
|
|
||||||
public UserStatus Status { get; set; } = UserStatus.Unverified;
|
public UserStatus Status { get; set; } = UserStatus.Unverified;
|
||||||
@@ -31,6 +33,7 @@ public class User
|
|||||||
public bool SupportPending { get; set; } = false;
|
public bool SupportPending { get; set; } = false;
|
||||||
public bool HasRated { get; set; } = false;
|
public bool HasRated { get; set; } = false;
|
||||||
public int Rating { get; set; } = 0;
|
public int Rating { get; set; } = 0;
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
public bool TotpEnabled { get; set; } = false;
|
public bool TotpEnabled { get; set; } = false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Interceptors;
|
namespace Moonlight.App.Database.Interceptors;
|
||||||
|
|
||||||
|
|||||||
1077
Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs
generated
Normal file
1077
Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedServerListLayoutToUser : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ServerListLayoutJson",
|
||||||
|
table: "Users",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ServerListLayoutJson",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedStreamerMode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -780,6 +780,10 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("Rating")
|
b.Property<int>("Rating")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ServerListLayoutJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<string>("State")
|
b.Property<string>("State")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -787,6 +791,9 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("Status")
|
b.Property<int>("Status")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("StreamerMode")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<int>("SubscriptionDuration")
|
b.Property<int>("SubscriptionDuration")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Events;
|
namespace Moonlight.App.Events;
|
||||||
|
|
||||||
|
|||||||
@@ -1,252 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Logging.Net;
|
|
||||||
using Logging.Net.Loggers.SB;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using ILogger = Logging.Net.ILogger;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
|
||||||
|
|
||||||
public class CacheLogger : ILogger
|
|
||||||
{
|
|
||||||
private SBLogger SbLogger = new();
|
|
||||||
private List<LogEntry> Messages = new();
|
|
||||||
|
|
||||||
public LogEntry[] GetMessages()
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
var result = new LogEntry[Messages.Count];
|
|
||||||
Messages.CopyTo(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear(int messages)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.RemoveRange(0, Math.Min(messages, Messages.Count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(string? s)
|
|
||||||
{
|
|
||||||
if (s == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "info",
|
|
||||||
Message = s
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.Info(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Debug(string? s)
|
|
||||||
{
|
|
||||||
if (s == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "debug",
|
|
||||||
Message = s
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.Debug(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Warn(string? s)
|
|
||||||
{
|
|
||||||
if (s == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "warn",
|
|
||||||
Message = s
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.Warn(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Error(string? s)
|
|
||||||
{
|
|
||||||
if (s == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "error",
|
|
||||||
Message = s
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.Error(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Fatal(string? s)
|
|
||||||
{
|
|
||||||
if (s == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "fatal",
|
|
||||||
Message = s
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.Fatal(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InfoEx(Exception ex)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "info",
|
|
||||||
Message = ex.ToStringDemystified()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.InfoEx(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DebugEx(Exception ex)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "debug",
|
|
||||||
Message = ex.ToStringDemystified()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.DebugEx(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WarnEx(Exception ex)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "warn",
|
|
||||||
Message = ex.ToStringDemystified()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.WarnEx(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ErrorEx(Exception ex)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "error",
|
|
||||||
Message = ex.ToStringDemystified()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.ErrorEx(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FatalEx(Exception ex)
|
|
||||||
{
|
|
||||||
lock (Messages)
|
|
||||||
{
|
|
||||||
Messages.Add(new()
|
|
||||||
{
|
|
||||||
Level = "fatal",
|
|
||||||
Message = ex.ToStringDemystified()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SbLogger.FatalEx(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingConfiguration GetErrorConfiguration()
|
|
||||||
{
|
|
||||||
return SbLogger.GetErrorConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetErrorConfiguration(LoggingConfiguration configuration)
|
|
||||||
{
|
|
||||||
SbLogger.SetErrorConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingConfiguration GetFatalConfiguration()
|
|
||||||
{
|
|
||||||
return SbLogger.GetFatalConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetFatalConfiguration(LoggingConfiguration configuration)
|
|
||||||
{
|
|
||||||
SbLogger.SetFatalConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingConfiguration GetWarnConfiguration()
|
|
||||||
{
|
|
||||||
return SbLogger.GetWarnConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetWarnConfiguration(LoggingConfiguration configuration)
|
|
||||||
{
|
|
||||||
SbLogger.SetWarnConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingConfiguration GetInfoConfiguration()
|
|
||||||
{
|
|
||||||
return SbLogger.GetInfoConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInfoConfiguration(LoggingConfiguration configuration)
|
|
||||||
{
|
|
||||||
SbLogger.SetInfoConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoggingConfiguration GetDebugConfiguration()
|
|
||||||
{
|
|
||||||
return SbLogger.GetDebugConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDebugConfiguration(LoggingConfiguration configuration)
|
|
||||||
{
|
|
||||||
SbLogger.SetDebugConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILoggingAddition GetAddition()
|
|
||||||
{
|
|
||||||
return SbLogger.GetAddition();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAddition(ILoggingAddition addition)
|
|
||||||
{
|
|
||||||
SbLogger.SetAddition(addition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool LogCallingClass
|
|
||||||
{
|
|
||||||
get => SbLogger.LogCallingClass;
|
|
||||||
set => SbLogger.LogCallingClass = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
@@ -82,19 +81,32 @@ public class DatabaseCheckupService
|
|||||||
Logger.Info($"Saving it to: {file}");
|
Logger.Info($"Saving it to: {file}");
|
||||||
Logger.Info("Starting backup...");
|
Logger.Info("Starting backup...");
|
||||||
|
|
||||||
var sw = new Stopwatch();
|
try
|
||||||
sw.Start();
|
{
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
|
||||||
await using MySqlConnection conn = new MySqlConnection(connectionString);
|
await using MySqlConnection conn = new MySqlConnection(connectionString);
|
||||||
await using MySqlCommand cmd = new MySqlCommand();
|
await using MySqlCommand cmd = new MySqlCommand();
|
||||||
using MySqlBackup mb = new MySqlBackup(cmd);
|
using MySqlBackup mb = new MySqlBackup(cmd);
|
||||||
|
|
||||||
cmd.Connection = conn;
|
cmd.Connection = conn;
|
||||||
await conn.OpenAsync();
|
await conn.OpenAsync();
|
||||||
mb.ExportToFile(file);
|
mb.ExportToFile(file);
|
||||||
await conn.CloseAsync();
|
await conn.CloseAsync();
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");
|
Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Fatal("-----------------------------------------------");
|
||||||
|
Logger.Fatal("Unable to create backup for moonlight database");
|
||||||
|
Logger.Fatal("Moonlight will start anyways in 30 seconds");
|
||||||
|
Logger.Fatal("-----------------------------------------------");
|
||||||
|
Logger.Fatal(e);
|
||||||
|
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(30));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Renci.SshNet;
|
||||||
using Renci.SshNet;
|
|
||||||
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers.Files;
|
namespace Moonlight.App.Helpers.Files;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Logging.Net;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
|||||||
108
Moonlight/App/Helpers/Logger.cs
Normal file
108
Moonlight/App/Helpers/Logger.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
#region String method calls
|
||||||
|
public static void Verbose(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Verbose("{Message}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Information("{Message}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Debug("{Message}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Error("{Message}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warn(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Warning("{Message}", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Fatal(string message, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Fatal("{Message}", message);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Exception method calls
|
||||||
|
public static void Verbose(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Verbose(exception, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Information(exception, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Debug(exception, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Error(exception, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warn(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Warning(exception, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Fatal(Exception exception, string channel = "default")
|
||||||
|
{
|
||||||
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
|
.Fatal(exception, "");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static string GetNameOfCallingClass(int skipFrames = 4)
|
||||||
|
{
|
||||||
|
string fullName;
|
||||||
|
Type declaringType;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
MethodBase method = new StackFrame(skipFrames, false).GetMethod();
|
||||||
|
declaringType = method.DeclaringType;
|
||||||
|
if (declaringType == null)
|
||||||
|
{
|
||||||
|
return method.Name;
|
||||||
|
}
|
||||||
|
skipFrames++;
|
||||||
|
if (declaringType.Name.Contains("<"))
|
||||||
|
fullName = declaringType.ReflectedType.Name;
|
||||||
|
else
|
||||||
|
fullName = declaringType.Name;
|
||||||
|
}
|
||||||
|
while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase) | fullName.Contains("Logger"));
|
||||||
|
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Logging.Net;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Logging.Net;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
|
||||||
|
|
||||||
public static class ParseHelper
|
public static class ParseHelper
|
||||||
{
|
{
|
||||||
|
|||||||
66
Moonlight/App/Helpers/Retry.cs
Normal file
66
Moonlight/App/Helpers/Retry.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class Retry
|
||||||
|
{
|
||||||
|
private List<Type> RetryExceptionTypes;
|
||||||
|
private List<Func<Exception, bool>> RetryFilters;
|
||||||
|
private int RetryTimes = 1;
|
||||||
|
|
||||||
|
public Retry()
|
||||||
|
{
|
||||||
|
RetryExceptionTypes = new();
|
||||||
|
RetryFilters = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry Times(int times)
|
||||||
|
{
|
||||||
|
RetryTimes = times;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry At(Func<Exception, bool> filter)
|
||||||
|
{
|
||||||
|
RetryFilters.Add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Retry At<T>()
|
||||||
|
{
|
||||||
|
RetryExceptionTypes.Add(typeof(T));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Call(Func<Task> method)
|
||||||
|
{
|
||||||
|
int triesLeft = RetryTimes;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await method.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if(triesLeft < 1) // Throw if no tries left
|
||||||
|
throw;
|
||||||
|
|
||||||
|
if (RetryExceptionTypes.Any(x => x.FullName == e.GetType().FullName))
|
||||||
|
{
|
||||||
|
triesLeft--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RetryFilters.Any(x => x.Invoke(e)))
|
||||||
|
{
|
||||||
|
triesLeft--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if not filtered -> unknown/unhandled
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} while (triesLeft >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.Helpers.Wings.Data;
|
using Moonlight.App.Helpers.Wings.Data;
|
||||||
using Moonlight.App.Helpers.Wings.Enums;
|
using Moonlight.App.Helpers.Wings.Enums;
|
||||||
using Moonlight.App.Helpers.Wings.Events;
|
using Moonlight.App.Helpers.Wings.Events;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
@@ -9,7 +8,6 @@ using Moonlight.App.Models.Notifications;
|
|||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
using Moonlight.App.Services.Notifications;
|
using Moonlight.App.Services.Notifications;
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight.Notifications;
|
namespace Moonlight.App.Http.Controllers.Api.Moonlight.Notifications;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Logging.Net;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
using System.Text;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Services;
|
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||||
|
|
||||||
@@ -14,13 +8,10 @@ namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
|||||||
[Route("api/moonlight/resources")]
|
[Route("api/moonlight/resources")]
|
||||||
public class ResourcesController : Controller
|
public class ResourcesController : Controller
|
||||||
{
|
{
|
||||||
private readonly SecurityLogService SecurityLogService;
|
|
||||||
private readonly BucketService BucketService;
|
private readonly BucketService BucketService;
|
||||||
|
|
||||||
public ResourcesController(SecurityLogService securityLogService,
|
public ResourcesController(BucketService bucketService)
|
||||||
BucketService bucketService)
|
|
||||||
{
|
{
|
||||||
SecurityLogService = securityLogService;
|
|
||||||
BucketService = bucketService;
|
BucketService = bucketService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,10 +20,7 @@ public class ResourcesController : Controller
|
|||||||
{
|
{
|
||||||
if (name.Contains(".."))
|
if (name.Contains(".."))
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
|
||||||
{
|
|
||||||
x.Add<string>(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
@@ -52,10 +40,7 @@ public class ResourcesController : Controller
|
|||||||
{
|
{
|
||||||
if (name.Contains(".."))
|
if (name.Contains(".."))
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
|
||||||
{
|
|
||||||
x.Add<string>(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
@@ -75,10 +60,7 @@ public class ResourcesController : Controller
|
|||||||
{
|
{
|
||||||
if (name.Contains(".."))
|
if (name.Contains(".."))
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.PathTransversal, x =>
|
Logger.Warn($"Detected an attempted path transversal. Path: {name}", "security");
|
||||||
{
|
|
||||||
x.Add<string>(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using Logging.Net;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Events;
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Http.Requests.Daemon;
|
using Moonlight.App.Http.Requests.Daemon;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
|
||||||
|
|
||||||
namespace Moonlight.App.LogMigrator;
|
namespace Moonlight.App.LogMigrator;
|
||||||
|
|
||||||
|
|||||||
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class UserPreferencesDataModel
|
||||||
|
{
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
}
|
||||||
8
Moonlight/App/Models/Misc/MalwareScanResult.cs
Normal file
8
Moonlight/App/Models/Misc/MalwareScanResult.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class MalwareScanResult
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public string Author { get; set; } = "";
|
||||||
|
}
|
||||||
7
Moonlight/App/Models/Misc/ServerGroup.cs
Normal file
7
Moonlight/App/Models/Misc/ServerGroup.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class ServerGroup
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public List<string> Servers { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
@@ -87,6 +86,13 @@ public class DiscordOAuth2Provider : OAuth2Provider
|
|||||||
|
|
||||||
var email = getData.GetValue<string>("email");
|
var email = getData.GetValue<string>("email");
|
||||||
var id = getData.GetValue<ulong>("id");
|
var id = getData.GetValue<ulong>("id");
|
||||||
|
var verified = getData.GetValue<bool>("verified");
|
||||||
|
|
||||||
|
if (!verified)
|
||||||
|
{
|
||||||
|
Logger.Warn("A user tried to use an unverified discord account to login", "security");
|
||||||
|
throw new DisplayException("You can only use verified discord accounts for oauth signin");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle data
|
// Handle data
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.ApiClients.Google.Requests;
|
using Moonlight.App.ApiClients.Google.Requests;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MineStatLib;
|
using MineStatLib;
|
||||||
using Moonlight.App.ApiClients.Daemon.Resources;
|
using Moonlight.App.ApiClients.Daemon.Resources;
|
||||||
using Moonlight.App.ApiClients.Wings;
|
using Moonlight.App.ApiClients.Wings;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Webhook;
|
using Discord.Webhook;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Events;
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Background;
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|||||||
196
Moonlight/App/Services/Background/MalwareScanService.cs
Normal file
196
Moonlight/App/Services/Background/MalwareScanService.cs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
using Moonlight.App.ApiClients.Daemon.Resources;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|
||||||
|
public class MalwareScanService
|
||||||
|
{
|
||||||
|
private Repository<Server> ServerRepository;
|
||||||
|
private Repository<Node> NodeRepository;
|
||||||
|
private NodeService NodeService;
|
||||||
|
private ServerService ServerService;
|
||||||
|
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public bool IsRunning { get; private set; }
|
||||||
|
public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
|
||||||
|
public string Status { get; private set; } = "N/A";
|
||||||
|
|
||||||
|
public MalwareScanService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
Event = eventSystem;
|
||||||
|
|
||||||
|
ScanResults = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Start()
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
throw new DisplayException("Malware scan is already running");
|
||||||
|
|
||||||
|
Task.Run(Run);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run()
|
||||||
|
{
|
||||||
|
IsRunning = true;
|
||||||
|
Status = "Clearing last results";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
lock (ScanResults)
|
||||||
|
{
|
||||||
|
ScanResults.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Event.Emit("malwareScan.result");
|
||||||
|
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
// Load services from di scope
|
||||||
|
NodeRepository = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
||||||
|
ServerRepository = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||||
|
NodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||||
|
ServerService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
||||||
|
|
||||||
|
var nodes = NodeRepository.Get().ToArray();
|
||||||
|
var containers = new List<Container>();
|
||||||
|
|
||||||
|
// Fetch and summarize all running containers from all nodes
|
||||||
|
Logger.Verbose("Fetching and summarizing all running containers from all nodes");
|
||||||
|
|
||||||
|
Status = "Fetching and summarizing all running containers from all nodes";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
var metrics = await NodeService.GetDockerMetrics(node);
|
||||||
|
|
||||||
|
foreach (var container in metrics.Containers)
|
||||||
|
{
|
||||||
|
containers.Add(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerServerMapped = new Dictionary<Server, Container>();
|
||||||
|
|
||||||
|
// Map all the containers to their corresponding server if existing
|
||||||
|
Logger.Verbose("Mapping all the containers to their corresponding server if existing");
|
||||||
|
|
||||||
|
Status = "Mapping all the containers to their corresponding server if existing";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
foreach (var container in containers)
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(container.Name, out Guid uuid))
|
||||||
|
{
|
||||||
|
var server = ServerRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Uuid == uuid);
|
||||||
|
|
||||||
|
if(server == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
containerServerMapped.Add(server, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform scan
|
||||||
|
var resultsMapped = new Dictionary<Server, MalwareScanResult[]>();
|
||||||
|
foreach (var mapping in containerServerMapped)
|
||||||
|
{
|
||||||
|
Logger.Verbose($"Scanning server {mapping.Key.Name} for malware");
|
||||||
|
|
||||||
|
Status = $"Scanning server {mapping.Key.Name} for malware";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
var results = await PerformScanOnServer(mapping.Key, mapping.Value);
|
||||||
|
|
||||||
|
if (results.Any())
|
||||||
|
{
|
||||||
|
resultsMapped.Add(mapping.Key, results);
|
||||||
|
Logger.Verbose($"{results.Length} findings on server {mapping.Key.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Verbose($"Scan complete. Detected {resultsMapped.Count} servers with findings");
|
||||||
|
|
||||||
|
IsRunning = false;
|
||||||
|
Status = $"Scan complete. Detected {resultsMapped.Count} servers with findings";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
lock (ScanResults)
|
||||||
|
{
|
||||||
|
foreach (var mapping in resultsMapped)
|
||||||
|
{
|
||||||
|
ScanResults.Add(mapping.Key, mapping.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Event.Emit("malwareScan.result");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MalwareScanResult[]> PerformScanOnServer(Server server, Container container)
|
||||||
|
{
|
||||||
|
var results = new List<MalwareScanResult>();
|
||||||
|
|
||||||
|
// TODO: Move scans to an universal format / api
|
||||||
|
|
||||||
|
// Define scans here
|
||||||
|
|
||||||
|
async Task ScanSelfBot()
|
||||||
|
{
|
||||||
|
var access = await ServerService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => x.Name == "tokens.txt"))
|
||||||
|
{
|
||||||
|
results.Add(new ()
|
||||||
|
{
|
||||||
|
Title = "Found SelfBot",
|
||||||
|
Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ScanFakePlayerPlugins()
|
||||||
|
{
|
||||||
|
var access = await ServerService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => !x.IsFile && x.Name == "plugins")) // Check for plugins folder
|
||||||
|
{
|
||||||
|
await access.Cd("plugins");
|
||||||
|
fileElements = await access.Ls();
|
||||||
|
|
||||||
|
foreach (var fileElement in fileElements)
|
||||||
|
{
|
||||||
|
if (fileElement.Name.ToLower().Contains("fake"))
|
||||||
|
{
|
||||||
|
results.Add(new()
|
||||||
|
{
|
||||||
|
Title = "Fake player plugin",
|
||||||
|
Description = $"Suspicious plugin file: {fileElement.Name}",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute scans
|
||||||
|
await ScanSelfBot();
|
||||||
|
await ScanFakePlayerPlugins();
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
@@ -42,8 +41,6 @@ public class ConfigService : IConfiguration
|
|||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
{
|
{
|
||||||
Logger.Info($"Reading config from '{PathBuilder.File("storage", "configs", "config.json")}'");
|
|
||||||
|
|
||||||
Configuration = new ConfigurationBuilder().AddJsonStream(
|
Configuration = new ConfigurationBuilder().AddJsonStream(
|
||||||
new MemoryStream(Encoding.ASCII.GetBytes(
|
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||||
File.ReadAllText(
|
File.ReadAllText(
|
||||||
@@ -51,8 +48,6 @@ public class ConfigService : IConfiguration
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)).Build();
|
)).Build();
|
||||||
|
|
||||||
Logger.Info("Reloaded configuration file");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IConfigurationSection> GetChildren()
|
public IEnumerable<IConfigurationSection> GetChildren()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Logging.Net;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.DiscordBot.Commands;
|
namespace Moonlight.App.Services.DiscordBot.Commands;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Services.DiscordBot.Commands;
|
using Moonlight.App.Services.DiscordBot.Commands;
|
||||||
using Moonlight.App.Services.DiscordBot.Modules;
|
using Moonlight.App.Services.DiscordBot.Modules;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.DiscordBot.Modules;
|
namespace Moonlight.App.Services.DiscordBot.Modules;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.ApiClients.Wings;
|
using Moonlight.App.ApiClients.Wings;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
using CloudFlare.Client;
|
using CloudFlare.Client;
|
||||||
using CloudFlare.Client.Api.Authentication;
|
using CloudFlare.Client.Api.Authentication;
|
||||||
|
using CloudFlare.Client.Api.Display;
|
||||||
using CloudFlare.Client.Api.Parameters.Data;
|
using CloudFlare.Client.Api.Parameters.Data;
|
||||||
using CloudFlare.Client.Api.Result;
|
using CloudFlare.Client.Api.Result;
|
||||||
using CloudFlare.Client.Api.Zones;
|
using CloudFlare.Client.Api.Zones;
|
||||||
using CloudFlare.Client.Api.Zones.DnsRecord;
|
using CloudFlare.Client.Api.Zones.DnsRecord;
|
||||||
using CloudFlare.Client.Enumerators;
|
using CloudFlare.Client.Enumerators;
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories.Domains;
|
using Moonlight.App.Repositories.Domains;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
|
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
|
||||||
|
using MatchType = CloudFlare.Client.Enumerators.MatchType;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
@@ -21,18 +22,15 @@ public class DomainService
|
|||||||
private readonly DomainRepository DomainRepository;
|
private readonly DomainRepository DomainRepository;
|
||||||
private readonly SharedDomainRepository SharedDomainRepository;
|
private readonly SharedDomainRepository SharedDomainRepository;
|
||||||
private readonly CloudFlareClient Client;
|
private readonly CloudFlareClient Client;
|
||||||
private readonly AuditLogService AuditLogService;
|
|
||||||
private readonly string AccountId;
|
private readonly string AccountId;
|
||||||
|
|
||||||
public DomainService(
|
public DomainService(
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
DomainRepository domainRepository,
|
DomainRepository domainRepository,
|
||||||
SharedDomainRepository sharedDomainRepository,
|
SharedDomainRepository sharedDomainRepository)
|
||||||
AuditLogService auditLogService)
|
|
||||||
{
|
{
|
||||||
DomainRepository = domainRepository;
|
DomainRepository = domainRepository;
|
||||||
SharedDomainRepository = sharedDomainRepository;
|
SharedDomainRepository = sharedDomainRepository;
|
||||||
AuditLogService = auditLogService;
|
|
||||||
|
|
||||||
var config = configService
|
var config = configService
|
||||||
.GetSection("Moonlight")
|
.GetSection("Moonlight")
|
||||||
@@ -97,9 +95,36 @@ public class DomainService
|
|||||||
{
|
{
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var records = GetData(
|
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
|
||||||
await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId)
|
|
||||||
);
|
// Load paginated
|
||||||
|
// TODO: Find an alternative option. This way to load the records is NOT optimal
|
||||||
|
// and can result in long loading time when there are many dns records.
|
||||||
|
// As cloudflare does not offer a way to search dns records which starts
|
||||||
|
// with a specific string we are not able to filter it using the api (client)
|
||||||
|
var initialResponse = await Client.Zones.DnsRecords.GetAsync(domain.SharedDomain.CloudflareId);
|
||||||
|
|
||||||
|
records.AddRange(GetData(initialResponse));
|
||||||
|
|
||||||
|
// Check if there are more pages
|
||||||
|
while (initialResponse.ResultInfo.Page < initialResponse.ResultInfo.TotalPage)
|
||||||
|
{
|
||||||
|
// Get the next page of data
|
||||||
|
var nextPageResponse = await Client.Zones.DnsRecords.GetAsync(
|
||||||
|
domain.SharedDomain.CloudflareId,
|
||||||
|
displayOptions: new()
|
||||||
|
{
|
||||||
|
Page = initialResponse.ResultInfo.Page + 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var nextPageRecords = GetData(nextPageResponse);
|
||||||
|
|
||||||
|
// Append the records from the next page to the existing records
|
||||||
|
records.AddRange(nextPageRecords);
|
||||||
|
|
||||||
|
// Update the initial response to the next page response
|
||||||
|
initialResponse = nextPageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
var dname = $".{rname}";
|
var dname = $".{rname}";
|
||||||
@@ -149,7 +174,11 @@ public class DomainService
|
|||||||
if (dnsRecord.Type == DnsRecordType.Srv)
|
if (dnsRecord.Type == DnsRecordType.Srv)
|
||||||
{
|
{
|
||||||
var parts = dnsRecord.Name.Split(".");
|
var parts = dnsRecord.Name.Split(".");
|
||||||
Enum.TryParse(parts[1], out Protocol protocol);
|
|
||||||
|
Protocol protocol = Protocol.Tcp;
|
||||||
|
|
||||||
|
if (parts[1].Contains("udp"))
|
||||||
|
protocol = Protocol.Udp;
|
||||||
|
|
||||||
var valueParts = dnsRecord.Content.Split(" ");
|
var valueParts = dnsRecord.Content.Split(" ");
|
||||||
|
|
||||||
@@ -191,11 +220,7 @@ public class DomainService
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.AddDomainRecord, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<Domain>(d.Id);
|
|
||||||
x.Add<DnsRecord>(dnsRecord.Name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
@@ -225,11 +250,7 @@ public class DomainService
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<Domain>(d.Id);
|
|
||||||
x.Add<DnsRecord>(dnsRecord.Name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
@@ -240,11 +261,7 @@ public class DomainService
|
|||||||
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
|
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
|
||||||
);
|
);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<Domain>(d.Id);
|
|
||||||
x.Add<DnsRecord>(dnsRecord.Name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Domain EnsureData(Domain domain)
|
private Domain EnsureData(Domain domain)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Helpers;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Files;
|
namespace Moonlight.App.Services.Files;
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
using Moonlight.App.Models.Log;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.LogServices;
|
|
||||||
|
|
||||||
public class AuditLogService
|
|
||||||
{
|
|
||||||
private readonly AuditLogEntryRepository Repository;
|
|
||||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
|
||||||
|
|
||||||
public AuditLogService(
|
|
||||||
AuditLogEntryRepository repository,
|
|
||||||
IHttpContextAccessor httpContextAccessor)
|
|
||||||
{
|
|
||||||
Repository = repository;
|
|
||||||
HttpContextAccessor = httpContextAccessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Log(AuditLogType type, Action<AuditLogParameters> data)
|
|
||||||
{
|
|
||||||
var ip = GetIp();
|
|
||||||
var al = new AuditLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new AuditLogEntry()
|
|
||||||
{
|
|
||||||
Ip = ip,
|
|
||||||
Type = type,
|
|
||||||
System = false,
|
|
||||||
JsonData = al.Build()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task LogSystem(AuditLogType type, Action<AuditLogParameters> data)
|
|
||||||
{
|
|
||||||
var al = new AuditLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new AuditLogEntry()
|
|
||||||
{
|
|
||||||
Type = type,
|
|
||||||
System = true,
|
|
||||||
JsonData = al.Build()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetIp()
|
|
||||||
{
|
|
||||||
if (HttpContextAccessor.HttpContext == null)
|
|
||||||
return "N/A";
|
|
||||||
|
|
||||||
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
|
|
||||||
{
|
|
||||||
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AuditLogParameters
|
|
||||||
{
|
|
||||||
private List<LogData> Data = new List<LogData>();
|
|
||||||
|
|
||||||
public void Add<T>(object? data)
|
|
||||||
{
|
|
||||||
if(data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Data.Add(new LogData()
|
|
||||||
{
|
|
||||||
Type = typeof(T),
|
|
||||||
Value = data.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string Build()
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Reflection;
|
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
using Moonlight.App.Models.Log;
|
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.LogServices;
|
|
||||||
|
|
||||||
public class ErrorLogService
|
|
||||||
{
|
|
||||||
private readonly ErrorLogEntryRepository Repository;
|
|
||||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
|
||||||
|
|
||||||
public ErrorLogService(ErrorLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
|
|
||||||
{
|
|
||||||
Repository = repository;
|
|
||||||
HttpContextAccessor = httpContextAccessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Log(Exception exception, Action<ErrorLogParameters> data)
|
|
||||||
{
|
|
||||||
var ip = GetIp();
|
|
||||||
var al = new ErrorLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new ErrorLogEntry()
|
|
||||||
{
|
|
||||||
Ip = ip,
|
|
||||||
System = false,
|
|
||||||
JsonData = al.Build(),
|
|
||||||
Class = NameOfCallingClass(),
|
|
||||||
Stacktrace = exception.ToStringDemystified()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task LogSystem(Exception exception, Action<ErrorLogParameters> data)
|
|
||||||
{
|
|
||||||
var al = new ErrorLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new ErrorLogEntry()
|
|
||||||
{
|
|
||||||
System = true,
|
|
||||||
JsonData = al.Build(),
|
|
||||||
Class = NameOfCallingClass(),
|
|
||||||
Stacktrace = exception.ToStringDemystified()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string NameOfCallingClass(int skipFrames = 4)
|
|
||||||
{
|
|
||||||
string fullName;
|
|
||||||
Type? declaringType;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
MethodBase method = new StackFrame(skipFrames, false).GetMethod()!;
|
|
||||||
declaringType = method.DeclaringType;
|
|
||||||
if (declaringType == null)
|
|
||||||
{
|
|
||||||
return method.Name;
|
|
||||||
}
|
|
||||||
skipFrames++;
|
|
||||||
if (declaringType.Name.Contains("<"))
|
|
||||||
fullName = declaringType.ReflectedType!.Name;
|
|
||||||
else
|
|
||||||
fullName = declaringType.Name;
|
|
||||||
}
|
|
||||||
while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase) | fullName.Contains("Log"));
|
|
||||||
|
|
||||||
return fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetIp()
|
|
||||||
{
|
|
||||||
if (HttpContextAccessor.HttpContext == null)
|
|
||||||
return "N/A";
|
|
||||||
|
|
||||||
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
|
|
||||||
{
|
|
||||||
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ErrorLogParameters
|
|
||||||
{
|
|
||||||
private List<LogData> Data = new List<LogData>();
|
|
||||||
|
|
||||||
public void Add<T>(object? data)
|
|
||||||
{
|
|
||||||
if(data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Data.Add(new LogData()
|
|
||||||
{
|
|
||||||
Type = typeof(T),
|
|
||||||
Value = data.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string Build()
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.Helpers;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.LogServices;
|
|
||||||
|
|
||||||
public class LogService
|
|
||||||
{
|
|
||||||
public LogService()
|
|
||||||
{
|
|
||||||
Task.Run(ClearLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ClearLog()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromMinutes(15));
|
|
||||||
|
|
||||||
if (GetMessages().Length > 500)
|
|
||||||
{
|
|
||||||
if (Logger.UsedLogger is CacheLogger cacheLogger)
|
|
||||||
{
|
|
||||||
cacheLogger.Clear(250); //TODO: config
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warn("Log service cannot access cache. Is Logging.Net using CacheLogger?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogEntry[] GetMessages()
|
|
||||||
{
|
|
||||||
if (Logger.UsedLogger is CacheLogger cacheLogger)
|
|
||||||
{
|
|
||||||
return cacheLogger.GetMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Warn("Log service cannot access cache. Is Logging.Net using CacheLogger?");
|
|
||||||
|
|
||||||
return Array.Empty<LogEntry>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
using Moonlight.App.Models.Log;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.LogServices;
|
|
||||||
|
|
||||||
public class SecurityLogService
|
|
||||||
{
|
|
||||||
private readonly SecurityLogEntryRepository Repository;
|
|
||||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
|
||||||
|
|
||||||
public SecurityLogService(SecurityLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
|
|
||||||
{
|
|
||||||
Repository = repository;
|
|
||||||
HttpContextAccessor = httpContextAccessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Log(SecurityLogType type, Action<SecurityLogParameters> data)
|
|
||||||
{
|
|
||||||
var ip = GetIp();
|
|
||||||
var al = new SecurityLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new SecurityLogEntry()
|
|
||||||
{
|
|
||||||
Ip = ip,
|
|
||||||
Type = type,
|
|
||||||
System = false,
|
|
||||||
JsonData = al.Build()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task LogSystem(SecurityLogType type, Action<SecurityLogParameters> data)
|
|
||||||
{
|
|
||||||
var al = new SecurityLogParameters();
|
|
||||||
data(al);
|
|
||||||
|
|
||||||
var entry = new SecurityLogEntry()
|
|
||||||
{
|
|
||||||
Type = type,
|
|
||||||
System = true,
|
|
||||||
JsonData = al.Build()
|
|
||||||
};
|
|
||||||
|
|
||||||
Repository.Add(entry);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetIp()
|
|
||||||
{
|
|
||||||
if (HttpContextAccessor.HttpContext == null)
|
|
||||||
return "N/A";
|
|
||||||
|
|
||||||
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
|
|
||||||
{
|
|
||||||
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class SecurityLogParameters
|
|
||||||
{
|
|
||||||
private List<LogData> Data = new List<LogData>();
|
|
||||||
|
|
||||||
public void Add<T>(object? data)
|
|
||||||
{
|
|
||||||
if(data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Data.Add(new LogData()
|
|
||||||
{
|
|
||||||
Type = typeof(T),
|
|
||||||
Value = data.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string Build()
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using MimeKit;
|
||||||
using MimeKit;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Mail;
|
namespace Moonlight.App.Services.Mail;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Logging.Net;
|
using Moonlight.App.Helpers;
|
||||||
using Octokit;
|
using Octokit;
|
||||||
using Repository = LibGit2Sharp.Repository;
|
using Repository = LibGit2Sharp.Repository;
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ using JWT.Builder;
|
|||||||
using JWT.Exceptions;
|
using JWT.Exceptions;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
@@ -14,15 +12,12 @@ public class OneTimeJwtService
|
|||||||
{
|
{
|
||||||
private readonly ConfigService ConfigService;
|
private readonly ConfigService ConfigService;
|
||||||
private readonly RevokeRepository RevokeRepository;
|
private readonly RevokeRepository RevokeRepository;
|
||||||
private readonly SecurityLogService SecurityLogService;
|
|
||||||
|
|
||||||
public OneTimeJwtService(ConfigService configService,
|
public OneTimeJwtService(ConfigService configService,
|
||||||
RevokeRepository revokeRepository,
|
RevokeRepository revokeRepository)
|
||||||
SecurityLogService securityLogService)
|
|
||||||
{
|
{
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
RevokeRepository = revokeRepository;
|
RevokeRepository = revokeRepository;
|
||||||
SecurityLogService = securityLogService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
|
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
|
||||||
@@ -76,10 +71,7 @@ public class OneTimeJwtService
|
|||||||
}
|
}
|
||||||
catch (SignatureVerificationException)
|
catch (SignatureVerificationException)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, x =>
|
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
|
||||||
{
|
|
||||||
x.Add<string>(token);
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.ApiClients.Wings;
|
using Moonlight.App.ApiClients.Wings;
|
||||||
using Moonlight.App.ApiClients.Wings.Requests;
|
using Moonlight.App.ApiClients.Wings.Requests;
|
||||||
using Moonlight.App.ApiClients.Wings.Resources;
|
using Moonlight.App.ApiClients.Wings.Resources;
|
||||||
@@ -13,7 +12,6 @@ using Moonlight.App.Helpers.Wings;
|
|||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
@@ -30,9 +28,6 @@ public class ServerService
|
|||||||
private readonly UserService UserService;
|
private readonly UserService UserService;
|
||||||
private readonly ConfigService ConfigService;
|
private readonly ConfigService ConfigService;
|
||||||
private readonly WingsJwtHelper WingsJwtHelper;
|
private readonly WingsJwtHelper WingsJwtHelper;
|
||||||
private readonly SecurityLogService SecurityLogService;
|
|
||||||
private readonly AuditLogService AuditLogService;
|
|
||||||
private readonly ErrorLogService ErrorLogService;
|
|
||||||
private readonly NodeService NodeService;
|
private readonly NodeService NodeService;
|
||||||
private readonly DateTimeService DateTimeService;
|
private readonly DateTimeService DateTimeService;
|
||||||
private readonly EventSystem Event;
|
private readonly EventSystem Event;
|
||||||
@@ -46,9 +41,6 @@ public class ServerService
|
|||||||
UserService userService,
|
UserService userService,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
WingsJwtHelper wingsJwtHelper,
|
WingsJwtHelper wingsJwtHelper,
|
||||||
SecurityLogService securityLogService,
|
|
||||||
AuditLogService auditLogService,
|
|
||||||
ErrorLogService errorLogService,
|
|
||||||
NodeService nodeService,
|
NodeService nodeService,
|
||||||
NodeAllocationRepository nodeAllocationRepository,
|
NodeAllocationRepository nodeAllocationRepository,
|
||||||
DateTimeService dateTimeService,
|
DateTimeService dateTimeService,
|
||||||
@@ -63,9 +55,6 @@ public class ServerService
|
|||||||
UserService = userService;
|
UserService = userService;
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
WingsJwtHelper = wingsJwtHelper;
|
WingsJwtHelper = wingsJwtHelper;
|
||||||
SecurityLogService = securityLogService;
|
|
||||||
AuditLogService = auditLogService;
|
|
||||||
ErrorLogService = errorLogService;
|
|
||||||
NodeService = nodeService;
|
NodeService = nodeService;
|
||||||
NodeAllocationRepository = nodeAllocationRepository;
|
NodeAllocationRepository = nodeAllocationRepository;
|
||||||
DateTimeService = dateTimeService;
|
DateTimeService = dateTimeService;
|
||||||
@@ -90,10 +79,20 @@ public class ServerService
|
|||||||
{
|
{
|
||||||
Server server = EnsureNodeData(s);
|
Server server = EnsureNodeData(s);
|
||||||
|
|
||||||
return await WingsApiHelper.Get<ServerDetails>(
|
ServerDetails result = null!;
|
||||||
server.Node,
|
|
||||||
$"api/servers/{server.Uuid}"
|
await new Retry()
|
||||||
);
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
result = await WingsApiHelper.Get<ServerDetails>(
|
||||||
|
server.Node,
|
||||||
|
$"api/servers/{server.Uuid}"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPowerState(Server s, PowerSignal signal)
|
public async Task SetPowerState(Server s, PowerSignal signal)
|
||||||
@@ -107,11 +106,7 @@ public class ServerService
|
|||||||
Action = rawSignal
|
Action = rawSignal
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ChangePowerState, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<Server>(server.Uuid);
|
|
||||||
x.Add<PowerSignal>(rawSignal);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerBackup> CreateBackup(Server server)
|
public async Task<ServerBackup> CreateBackup(Server server)
|
||||||
@@ -124,7 +119,8 @@ public class ServerService
|
|||||||
|
|
||||||
var backup = new ServerBackup()
|
var backup = new ServerBackup()
|
||||||
{
|
{
|
||||||
Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
Name =
|
||||||
|
$"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
||||||
Uuid = Guid.NewGuid(),
|
Uuid = Guid.NewGuid(),
|
||||||
CreatedAt = DateTimeService.GetCurrent(),
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
Created = false
|
Created = false
|
||||||
@@ -140,12 +136,7 @@ public class ServerService
|
|||||||
Ignore = ""
|
Ignore = ""
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.CreateBackup,
|
//TODO: AuditLog
|
||||||
x =>
|
|
||||||
{
|
|
||||||
x.Add<Server>(server.Uuid);
|
|
||||||
x.Add<ServerBackup>(backup.Uuid);
|
|
||||||
});
|
|
||||||
|
|
||||||
return backup;
|
return backup;
|
||||||
}
|
}
|
||||||
@@ -182,12 +173,7 @@ public class ServerService
|
|||||||
Adapter = "wings"
|
Adapter = "wings"
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.RestoreBackup,
|
//TODO: AuditLog
|
||||||
x =>
|
|
||||||
{
|
|
||||||
x.Add<Server>(server.Uuid);
|
|
||||||
x.Add<ServerBackup>(serverBackup.Uuid);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
||||||
@@ -200,8 +186,15 @@ public class ServerService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
await new Retry()
|
||||||
null);
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
await WingsApiHelper.Delete(serverData.Node,
|
||||||
|
$"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
||||||
|
null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (WingsException e)
|
catch (WingsException e)
|
||||||
{
|
{
|
||||||
@@ -220,13 +213,7 @@ public class ServerService
|
|||||||
|
|
||||||
await Event.Emit("wings.backups.delete", backup);
|
await Event.Emit("wings.backups.delete", backup);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
//TODO: AuditLog
|
||||||
x =>
|
|
||||||
{
|
|
||||||
x.Add<Server>(server.Uuid);
|
|
||||||
x.Add<ServerBackup>(backup.Uuid);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
||||||
@@ -239,12 +226,7 @@ public class ServerService
|
|||||||
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
|
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DownloadBackup,
|
//TODO: AuditLog
|
||||||
x =>
|
|
||||||
{
|
|
||||||
x.Add<Server>(server.Uuid);
|
|
||||||
x.Add<ServerBackup>(serverBackup.Uuid);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (server.Node.Ssl)
|
if (server.Node.Ssl)
|
||||||
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
|
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
|
||||||
@@ -293,7 +275,8 @@ public class ServerService
|
|||||||
|
|
||||||
freeAllocations = NodeAllocationRepository
|
freeAllocations = NodeAllocationRepository
|
||||||
.Get()
|
.Get()
|
||||||
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
.FromSqlRaw(
|
||||||
|
$"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -321,7 +304,8 @@ public class ServerService
|
|||||||
Allocations = freeAllocations.ToList(),
|
Allocations = freeAllocations.ToList(),
|
||||||
Backups = new(),
|
Backups = new(),
|
||||||
OverrideStartup = "",
|
OverrideStartup = "",
|
||||||
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default)
|
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default),
|
||||||
|
Installing = true
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var imageVariable in image.Variables)
|
foreach (var imageVariable in image.Variables)
|
||||||
@@ -340,23 +324,26 @@ public class ServerService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
await new Retry()
|
||||||
{
|
.Times(3)
|
||||||
Uuid = newServerData.Uuid,
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
StartOnCompletion = false
|
.Call(async () =>
|
||||||
});
|
{
|
||||||
|
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
||||||
|
{
|
||||||
|
Uuid = newServerData.Uuid,
|
||||||
|
StartOnCompletion = false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.CreateServer, x => { x.Add<Server>(newServerData.Uuid); });
|
//TODO: AuditLog
|
||||||
|
|
||||||
return newServerData;
|
return newServerData;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e, x =>
|
Logger.Error("Error creating server on wings");
|
||||||
{
|
Logger.Error(e);
|
||||||
x.Add<Server>(newServerData.Uuid);
|
|
||||||
x.Add<Node>(node.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff
|
ServerRepository.Delete(newServerData); //TODO Remove unsinged table stuff
|
||||||
|
|
||||||
@@ -373,7 +360,7 @@ public class ServerService
|
|||||||
server.Installing = true;
|
server.Installing = true;
|
||||||
ServerRepository.Update(server);
|
ServerRepository.Update(server);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ReinstallServer, x => { x.Add<Server>(server.Uuid); });
|
//TODO: AuditLog
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
||||||
@@ -382,7 +369,7 @@ public class ServerService
|
|||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x => { x.Add<int>(id); });
|
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
|
||||||
throw new Exception("Server not found");
|
throw new Exception("Server not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,8 +395,6 @@ public class ServerService
|
|||||||
|
|
||||||
public async Task Delete(Server s)
|
public async Task Delete(Server s)
|
||||||
{
|
{
|
||||||
throw new DisplayException("Deleting servers is currently disabled");
|
|
||||||
|
|
||||||
var backups = await GetBackups(s);
|
var backups = await GetBackups(s);
|
||||||
|
|
||||||
foreach (var backup in backups)
|
foreach (var backup in backups)
|
||||||
@@ -430,7 +415,21 @@ public class ServerService
|
|||||||
.Include(x => x.Node)
|
.Include(x => x.Node)
|
||||||
.First(x => x.Id == s.Id);
|
.First(x => x.Id == s.Id);
|
||||||
|
|
||||||
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
|
try
|
||||||
|
{
|
||||||
|
await new Retry()
|
||||||
|
.Times(3)
|
||||||
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
|
.Call(async () =>
|
||||||
|
{
|
||||||
|
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (WingsException e)
|
||||||
|
{
|
||||||
|
if (e.StatusCode != 404)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var variable in server.Variables.ToArray())
|
foreach (var variable in server.Variables.ToArray())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Moonlight.App.Services.Files;
|
||||||
using Moonlight.App.Services.Files;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Sessions;
|
namespace Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
using JWT.Algorithms;
|
using JWT.Algorithms;
|
||||||
using JWT.Builder;
|
using JWT.Builder;
|
||||||
using JWT.Exceptions;
|
using JWT.Exceptions;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using UAParser;
|
using UAParser;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Sessions;
|
namespace Moonlight.App.Services.Sessions;
|
||||||
@@ -16,8 +14,6 @@ public class IdentityService
|
|||||||
{
|
{
|
||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly CookieService CookieService;
|
private readonly CookieService CookieService;
|
||||||
private readonly SecurityLogService SecurityLogService;
|
|
||||||
private readonly ErrorLogService ErrorLogService;
|
|
||||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
private readonly IHttpContextAccessor HttpContextAccessor;
|
||||||
private readonly string Secret;
|
private readonly string Secret;
|
||||||
|
|
||||||
@@ -27,15 +23,11 @@ public class IdentityService
|
|||||||
CookieService cookieService,
|
CookieService cookieService,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ConfigService configService,
|
ConfigService configService)
|
||||||
SecurityLogService securityLogService,
|
|
||||||
ErrorLogService errorLogService)
|
|
||||||
{
|
{
|
||||||
CookieService = cookieService;
|
CookieService = cookieService;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
HttpContextAccessor = httpContextAccessor;
|
HttpContextAccessor = httpContextAccessor;
|
||||||
SecurityLogService = securityLogService;
|
|
||||||
ErrorLogService = errorLogService;
|
|
||||||
|
|
||||||
Secret = configService
|
Secret = configService
|
||||||
.GetSection("Moonlight")
|
.GetSection("Moonlight")
|
||||||
@@ -90,15 +82,13 @@ public class IdentityService
|
|||||||
}
|
}
|
||||||
catch (SignatureVerificationException)
|
catch (SignatureVerificationException)
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, x =>
|
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
|
||||||
{
|
|
||||||
x.Add<string>(token);
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e, x => {});
|
Logger.Error("Error reading jwt");
|
||||||
|
Logger.Error(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +124,8 @@ public class IdentityService
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await ErrorLogService.Log(e, x => {});
|
Logger.Error("Unexpected error while processing token");
|
||||||
|
Logger.Error(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
Moonlight/App/Services/Sessions/KeyListenerService.cs
Normal file
38
Moonlight/App/Services/Sessions/KeyListenerService.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
public class KeyListenerService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
private DotNetObjectReference<KeyListenerService> _objRef;
|
||||||
|
|
||||||
|
public event EventHandler<string> KeyPressed;
|
||||||
|
|
||||||
|
public KeyListenerService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
|
{
|
||||||
|
_objRef = DotNetObjectReference.Create(this);
|
||||||
|
await _jsRuntime.InvokeVoidAsync("moonlight.keyListener.register", _objRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public void OnKeyPress(string key)
|
||||||
|
{
|
||||||
|
KeyPressed?.Invoke(this, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _jsRuntime.InvokeVoidAsync("moonlight.keyListener.unregister", _objRef);
|
||||||
|
_objRef.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception) { /* ignored */}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ public class SmartTranslateService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logging.Net.Logger.Error(ex);
|
Logger.Error(ex);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Logging.Net;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
@@ -66,8 +65,6 @@ public class StatisticsCaptureService
|
|||||||
AddEntry("databasesCount", databasesRepo.Get().Count());
|
AddEntry("databasesCount", databasesRepo.Get().Count());
|
||||||
AddEntry("sessionsCount", sessionService.GetAll().Length);
|
AddEntry("sessionsCount", sessionService.GetAll().Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log("Statistics are weird");
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Logging.Net;
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
using Microsoft.AspNetCore.Components.Forms;
|
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Events;
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
|
|
||||||
@@ -11,16 +9,13 @@ public class TotpService
|
|||||||
{
|
{
|
||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly AuditLogService AuditLogService;
|
|
||||||
|
|
||||||
public TotpService(
|
public TotpService(
|
||||||
IdentityService identityService,
|
IdentityService identityService,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository)
|
||||||
AuditLogService auditLogService)
|
|
||||||
{
|
{
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
AuditLogService = auditLogService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> Verify(string secret, string code)
|
public Task<bool> Verify(string secret, string code)
|
||||||
@@ -44,24 +39,24 @@ public class TotpService
|
|||||||
return user!.TotpSecret;
|
return user!.TotpSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Enable()
|
public async Task GenerateSecret()
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpSecret = GenerateSecret();
|
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.EnableTotp, x =>
|
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnforceTotpLogin()
|
public async Task Enable(string code)
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
|
if (!await Verify(user.TotpSecret, code))
|
||||||
|
{
|
||||||
|
throw new DisplayException("The 2fa code you entered is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
user.TotpEnabled = true;
|
user.TotpEnabled = true;
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
@@ -71,17 +66,10 @@ public class TotpService
|
|||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpEnabled = false;
|
user.TotpEnabled = false;
|
||||||
|
user.TotpSecret = "";
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DisableTotp,x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateSecret()
|
|
||||||
{
|
|
||||||
return Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ using Moonlight.App.Exceptions;
|
|||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using Moonlight.App.Services.Mail;
|
using Moonlight.App.Services.Mail;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
@@ -15,8 +14,6 @@ public class UserService
|
|||||||
{
|
{
|
||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly TotpService TotpService;
|
private readonly TotpService TotpService;
|
||||||
private readonly SecurityLogService SecurityLogService;
|
|
||||||
private readonly AuditLogService AuditLogService;
|
|
||||||
private readonly MailService MailService;
|
private readonly MailService MailService;
|
||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly IpLocateService IpLocateService;
|
private readonly IpLocateService IpLocateService;
|
||||||
@@ -28,8 +25,6 @@ public class UserService
|
|||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
TotpService totpService,
|
TotpService totpService,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
SecurityLogService securityLogService,
|
|
||||||
AuditLogService auditLogService,
|
|
||||||
MailService mailService,
|
MailService mailService,
|
||||||
IdentityService identityService,
|
IdentityService identityService,
|
||||||
IpLocateService ipLocateService,
|
IpLocateService ipLocateService,
|
||||||
@@ -37,8 +32,6 @@ public class UserService
|
|||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
TotpService = totpService;
|
TotpService = totpService;
|
||||||
SecurityLogService = securityLogService;
|
|
||||||
AuditLogService = auditLogService;
|
|
||||||
MailService = mailService;
|
MailService = mailService;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
IpLocateService = ipLocateService;
|
IpLocateService = ipLocateService;
|
||||||
@@ -85,10 +78,7 @@ public class UserService
|
|||||||
|
|
||||||
await MailService.SendMail(user!, "register", values => {});
|
await MailService.SendMail(user!, "register", values => {});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.Register, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
|
|
||||||
return await GenerateToken(user);
|
return await GenerateToken(user);
|
||||||
}
|
}
|
||||||
@@ -102,11 +92,7 @@ public class UserService
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
|
||||||
{
|
|
||||||
x.Add<User>(email);
|
|
||||||
x.Add<string>(password);
|
|
||||||
});
|
|
||||||
throw new DisplayException("Email and password combination not found");
|
throw new DisplayException("Email and password combination not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +101,7 @@ public class UserService
|
|||||||
return user.TotpEnabled;
|
return user.TotpEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
|
||||||
{
|
|
||||||
x.Add<User>(email);
|
|
||||||
x.Add<string>(password);
|
|
||||||
});
|
|
||||||
throw new DisplayException("Email and password combination not found");;
|
throw new DisplayException("Email and password combination not found");;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,28 +126,18 @@ public class UserService
|
|||||||
|
|
||||||
if (totpCodeValid)
|
if (totpCodeValid)
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.Login, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(email);
|
|
||||||
});
|
|
||||||
return await GenerateToken(user, true);
|
return await GenerateToken(user, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await SecurityLogService.Log(SecurityLogType.LoginFail, x =>
|
Logger.Warn($"Failed login attempt. Email: {email} Password: {password}", "security");
|
||||||
{
|
|
||||||
x.Add<User>(email);
|
|
||||||
x.Add<string>(password);
|
|
||||||
});
|
|
||||||
throw new DisplayException("2FA code invalid");
|
throw new DisplayException("2FA code invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.Login, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(email);
|
|
||||||
});
|
|
||||||
return await GenerateToken(user!, true);
|
return await GenerateToken(user!, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,10 +150,7 @@ public class UserService
|
|||||||
|
|
||||||
if (isSystemAction)
|
if (isSystemAction)
|
||||||
{
|
{
|
||||||
await AuditLogService.LogSystem(AuditLogType.ChangePassword, x=>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -194,10 +163,7 @@ public class UserService
|
|||||||
values.Add("Location", location);
|
values.Add("Location", location);
|
||||||
});
|
});
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ChangePassword, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,28 +173,18 @@ public class UserService
|
|||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
|
||||||
{
|
|
||||||
x.Add<int>(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
throw new Exception("Invalid username");
|
throw new Exception("Invalid username");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
|
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
|
||||||
{
|
{
|
||||||
await AuditLogService.LogSystem(AuditLogType.Login, x =>
|
//TODO: AuditLog
|
||||||
{
|
|
||||||
x.Add<User>(user.Email);
|
|
||||||
});
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, x =>
|
Logger.Warn($"Detected an sftp bruteforce attempt. ID: {id} Password: {password}", "security");
|
||||||
{
|
|
||||||
x.Add<int>(id);
|
|
||||||
x.Add<string>(password);
|
|
||||||
});
|
|
||||||
throw new Exception("Invalid userid or password");
|
throw new Exception("Invalid userid or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +227,7 @@ public class UserService
|
|||||||
var newPassword = StringHelper.GenerateString(16);
|
var newPassword = StringHelper.GenerateString(16);
|
||||||
await ChangePassword(user, newPassword, true);
|
await ChangePassword(user, newPassword, true);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.PasswordReset, x => {});
|
//TODO: AuditLog
|
||||||
|
|
||||||
var location = await IpLocateService.GetLocation();
|
var location = await IpLocateService.GetLocation();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Net;
|
using DnsClient;
|
||||||
using DnsClient;
|
|
||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.ApiClients.CloudPanel;
|
using Moonlight.App.ApiClients.CloudPanel;
|
||||||
using Moonlight.App.ApiClients.CloudPanel.Requests;
|
using Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
|
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
|
||||||
<PackageReference Include="JWT" Version="10.0.2" />
|
<PackageReference Include="JWT" Version="10.0.2" />
|
||||||
<PackageReference Include="LibGit2Sharp" Version="0.27.2" />
|
<PackageReference Include="LibGit2Sharp" Version="0.27.2" />
|
||||||
<PackageReference Include="Logging.Net" Version="1.1.3" />
|
|
||||||
<PackageReference Include="MailKit" Version="4.0.0" />
|
<PackageReference Include="MailKit" Version="4.0.0" />
|
||||||
<PackageReference Include="Mappy.Net" Version="1.0.2" />
|
<PackageReference Include="Mappy.Net" Version="1.0.2" />
|
||||||
<PackageReference Include="Markdig" Version="0.31.0" />
|
<PackageReference Include="Markdig" Version="0.31.0" />
|
||||||
@@ -47,6 +46,9 @@
|
|||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
|
||||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||||
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
||||||
|
|||||||
@@ -103,6 +103,8 @@
|
|||||||
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
||||||
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
|
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@@shopify/draggable@1.0.0-beta.11/lib/draggable.bundle.js"></script>
|
||||||
|
|
||||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"></script>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using BlazorDownloadFile;
|
|||||||
using BlazorTable;
|
using BlazorTable;
|
||||||
using CurrieTechnologies.Razor.SweetAlert2;
|
using CurrieTechnologies.Razor.SweetAlert2;
|
||||||
using HealthChecks.UI.Client;
|
using HealthChecks.UI.Client;
|
||||||
using Logging.Net;
|
|
||||||
using Moonlight.App.ApiClients.CloudPanel;
|
using Moonlight.App.ApiClients.CloudPanel;
|
||||||
using Moonlight.App.ApiClients.Daemon;
|
using Moonlight.App.ApiClients.Daemon;
|
||||||
using Moonlight.App.ApiClients.Modrinth;
|
using Moonlight.App.ApiClients.Modrinth;
|
||||||
@@ -24,13 +23,14 @@ using Moonlight.App.Services.Background;
|
|||||||
using Moonlight.App.Services.DiscordBot;
|
using Moonlight.App.Services.DiscordBot;
|
||||||
using Moonlight.App.Services.Files;
|
using Moonlight.App.Services.Files;
|
||||||
using Moonlight.App.Services.Interop;
|
using Moonlight.App.Services.Interop;
|
||||||
using Moonlight.App.Services.LogServices;
|
|
||||||
using Moonlight.App.Services.Mail;
|
using Moonlight.App.Services.Mail;
|
||||||
using Moonlight.App.Services.Minecraft;
|
using Moonlight.App.Services.Minecraft;
|
||||||
using Moonlight.App.Services.Notifications;
|
using Moonlight.App.Services.Notifications;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using Moonlight.App.Services.Statistics;
|
using Moonlight.App.Services.Statistics;
|
||||||
using Moonlight.App.Services.SupportChat;
|
using Moonlight.App.Services.SupportChat;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Sinks.SystemConsole.Themes;
|
||||||
|
|
||||||
namespace Moonlight
|
namespace Moonlight
|
||||||
{
|
{
|
||||||
@@ -38,14 +38,31 @@ namespace Moonlight
|
|||||||
{
|
{
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
Logger.UsedLogger = new CacheLogger();
|
// This will also copy all default config files
|
||||||
|
var configService = new ConfigService(new StorageService());
|
||||||
|
|
||||||
|
if (configService.DebugMode)
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Verbose()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(
|
||||||
|
outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
|
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
|
||||||
|
|
||||||
Logger.Info("Running pre-init tasks");
|
Logger.Info("Running pre-init tasks");
|
||||||
|
|
||||||
// This will also copy all default config files
|
|
||||||
var configService = new ConfigService(new StorageService());
|
|
||||||
var databaseCheckupService = new DatabaseCheckupService(configService);
|
var databaseCheckupService = new DatabaseCheckupService(configService);
|
||||||
|
|
||||||
await databaseCheckupService.Perform();
|
await databaseCheckupService.Perform();
|
||||||
@@ -134,15 +151,12 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<OAuth2Service>();
|
builder.Services.AddSingleton<OAuth2Service>();
|
||||||
builder.Services.AddScoped<DynamicBackgroundService>();
|
builder.Services.AddScoped<DynamicBackgroundService>();
|
||||||
builder.Services.AddScoped<ServerAddonPluginService>();
|
builder.Services.AddScoped<ServerAddonPluginService>();
|
||||||
|
builder.Services.AddScoped<KeyListenerService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<SubscriptionService>();
|
builder.Services.AddScoped<SubscriptionService>();
|
||||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
builder.Services.AddScoped<SecurityLogService>();
|
|
||||||
builder.Services.AddScoped<AuditLogService>();
|
|
||||||
builder.Services.AddScoped<ErrorLogService>();
|
|
||||||
builder.Services.AddScoped<LogService>();
|
|
||||||
builder.Services.AddScoped<MailService>();
|
builder.Services.AddScoped<MailService>();
|
||||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||||
|
|
||||||
@@ -168,6 +182,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<StatisticsCaptureService>();
|
builder.Services.AddSingleton<StatisticsCaptureService>();
|
||||||
builder.Services.AddSingleton<DiscordNotificationService>();
|
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||||
builder.Services.AddSingleton<CleanupService>();
|
builder.Services.AddSingleton<CleanupService>();
|
||||||
|
builder.Services.AddSingleton<MalwareScanService>();
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
builder.Services.AddSingleton<MoonlightService>();
|
builder.Services.AddSingleton<MoonlightService>();
|
||||||
@@ -206,6 +221,7 @@ namespace Moonlight
|
|||||||
_ = app.Services.GetRequiredService<DiscordBotService>();
|
_ = app.Services.GetRequiredService<DiscordBotService>();
|
||||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||||
|
_ = app.Services.GetRequiredService<MalwareScanService>();
|
||||||
|
|
||||||
_ = app.Services.GetRequiredService<MoonlightService>();
|
_ = app.Services.GetRequiredService<MoonlightService>();
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,20 @@
|
|||||||
},
|
},
|
||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
|
},
|
||||||
|
"Watch": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"workingDirectory": "$(ProjectDir)",
|
||||||
|
"hotReloadEnabled": true,
|
||||||
|
"hotReloadProfile": "aspnetcore",
|
||||||
|
"commandLineArgs": "watch run",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"ML_DEBUG": "true"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
31
Moonlight/Shared/Components/Alerts/NotFoundAlert.razor
Normal file
31
Moonlight/Shared/Components/Alerts/NotFoundAlert.razor
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div class="mx-auto">
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-content-center pt-5">
|
||||||
|
<img height="300" width="300" src="/assets/media/svg/notfound.svg" alt="Not found"/>
|
||||||
|
</div>
|
||||||
|
<span class="card-title text-center fs-3">
|
||||||
|
<TL>The requested resource was not found</TL>
|
||||||
|
</span>
|
||||||
|
<p class="card-body text-center fs-4 text-gray-800">
|
||||||
|
<TL>We were not able to find the requested resource. This can have following reasons</TL>
|
||||||
|
|
||||||
|
<div class="mt-4 d-flex flex-column align-items-center">
|
||||||
|
<li class="d-flex align-items-center py-2">
|
||||||
|
<span class="bullet me-5"></span> <TL>The resource was deleted</TL>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex align-items-center py-2">
|
||||||
|
<span class="bullet me-5"></span> <TL>You have to permission to access this resource</TL>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex align-items-center py-2">
|
||||||
|
<span class="bullet me-5"></span> <TL>You may have entered invalid data</TL>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex align-items-center py-2">
|
||||||
|
<span class="bullet me-5"></span> <TL>A unknown bug occured</TL>
|
||||||
|
</li>
|
||||||
|
<li class="d-flex align-items-center py-2">
|
||||||
|
<span class="bullet me-5"></span> <TL>An api was down and not proper handled</TL>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Models.Forms
|
@using Moonlight.App.Models.Forms
|
||||||
|
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
{
|
{
|
||||||
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
||||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent"></InputText>
|
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent" placeholder="@(SmartTranslateService.Translate("2fa code"))"></InputText>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid mb-10">
|
<div class="d-grid mb-10">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Services.LogServices
|
|
||||||
@using Moonlight.App.Services.Sessions
|
|
||||||
|
|
||||||
@inherits ErrorBoundary
|
|
||||||
|
|
||||||
@inject ErrorLogService ErrorLogService
|
|
||||||
|
|
||||||
@if (CurrentException is null)
|
|
||||||
{
|
|
||||||
@ChildContent
|
|
||||||
}
|
|
||||||
else if (ErrorContent is not null)
|
|
||||||
{
|
|
||||||
<div class="card card-flush h-md-100">
|
|
||||||
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
|
|
||||||
<div class="mb-10">
|
|
||||||
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
|
|
||||||
<span class="me-2">
|
|
||||||
<TL>Ooops. This component is crashed</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<TL>This component is crashed. The error has been reported to the moonlight team</TL>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="card card-flush h-md-100">
|
|
||||||
<div class="card-body d-flex flex-column justify-content-between mt-9 bgi-no-repeat bgi-size-cover bgi-position-x-center pb-0">
|
|
||||||
<div class="mb-10">
|
|
||||||
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
|
|
||||||
<span class="me-2">
|
|
||||||
<TL>Ooops. This component is crashed</TL>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<TL>This component is crashed. The error has been reported to the moonlight team</TL>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
List<Exception> receivedExceptions = new();
|
|
||||||
|
|
||||||
protected override async Task OnErrorAsync(Exception exception)
|
|
||||||
{
|
|
||||||
receivedExceptions.Add(exception);
|
|
||||||
|
|
||||||
await ErrorLogService.Log(exception, x => {});
|
|
||||||
|
|
||||||
await base.OnErrorAsync(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public new void Recover()
|
|
||||||
{
|
|
||||||
receivedExceptions.Clear();
|
|
||||||
base.Recover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@using Logging.Net
|
@using Moonlight.App.Services.Sessions
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
@inherits ErrorBoundary
|
@inherits ErrorBoundary
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@using Logging.Net
|
@using Moonlight.App.Exceptions
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.ApiClients.CloudPanel
|
@using Moonlight.App.ApiClients.CloudPanel
|
||||||
@using Moonlight.App.ApiClients.Daemon
|
@using Moonlight.App.ApiClients.Daemon
|
||||||
@using Moonlight.App.ApiClients.Modrinth
|
@using Moonlight.App.ApiClients.Modrinth
|
||||||
@using Moonlight.App.ApiClients.Wings
|
@using Moonlight.App.ApiClients.Wings
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@inherits ErrorBoundaryBase
|
@inherits ErrorBoundaryBase
|
||||||
|
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -42,7 +42,7 @@ else
|
|||||||
{
|
{
|
||||||
if (ConfigService.DebugMode)
|
if (ConfigService.DebugMode)
|
||||||
{
|
{
|
||||||
Logger.Warn(exception);
|
Logger.Verbose(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is DisplayException displayException)
|
if (exception is DisplayException displayException)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject ModalService ModalService
|
||||||
|
|
||||||
|
<div class="modal fade" id="connectionDetails" tabindex="-1" aria-labelledby="connectionDetails" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<TL>Connection details</TL>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Host</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Port</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Port)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row fv-row mb-7">
|
||||||
|
<div class="col-md-3 text-md-start">
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Username</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Username)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<TL>Close</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
private string Host = "";
|
||||||
|
private string Username = "";
|
||||||
|
private int Port;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri uri = new Uri(await Access.GetLaunchUrl());
|
||||||
|
|
||||||
|
Host = uri.Host;
|
||||||
|
Port = uri.Port;
|
||||||
|
Username = uri.UserInfo.Split(':')[0];
|
||||||
|
}
|
||||||
|
catch (NotImplementedException)
|
||||||
|
{
|
||||||
|
Host = "N/A";
|
||||||
|
Port = -1;
|
||||||
|
Username = "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Show()
|
||||||
|
{
|
||||||
|
await ModalService.Show("connectionDetails");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
@using BlazorMonaco
|
@using BlazorMonaco
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Moonlight.App.Services.Sessions
|
||||||
@using Moonlight.Shared.Components.Partials
|
@using Moonlight.Shared.Components.Partials
|
||||||
|
|
||||||
@inject SmartTranslateService TranslationService
|
@inject SmartTranslateService TranslationService
|
||||||
|
@inject KeyListenerService KeyListenerService
|
||||||
@inject IJSRuntime JsRuntime
|
@inject IJSRuntime JsRuntime
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
<div class="card bg-black rounded">
|
<div class="card bg-black rounded">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
||||||
@@ -65,6 +70,16 @@
|
|||||||
},
|
},
|
||||||
AutoIndent = true
|
AutoIndent = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
KeyListenerService.KeyPressed += KeyPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void KeyPressed(object? sender, string e)
|
||||||
|
{
|
||||||
|
if (e == "saveShortcut")
|
||||||
|
{
|
||||||
|
await Submit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
@@ -111,4 +126,10 @@
|
|||||||
{
|
{
|
||||||
return await Editor.GetValue();
|
return await Editor.GetValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Editor.Dispose();
|
||||||
|
KeyListenerService.KeyPressed -= KeyPressed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using BlazorDownloadFile
|
@using BlazorDownloadFile
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
InitialData="@EditorInitialData"
|
InitialData="@EditorInitialData"
|
||||||
Language="@EditorLanguage"
|
Language="@EditorLanguage"
|
||||||
OnCancel="() => Cancel()"
|
OnCancel="() => Cancel()"
|
||||||
OnSubmit="(_) => Cancel(true)"
|
OnSubmit="(_) => Save()"
|
||||||
HideControls="false">
|
HideControls="false">
|
||||||
</FileEditor>
|
</FileEditor>
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,7 @@ else
|
|||||||
<div class="card-header border-0 my-2">
|
<div class="card-header border-0 my-2">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
|
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-toolbar">
|
<div class="card-toolbar">
|
||||||
@@ -35,7 +34,9 @@ else
|
|||||||
@if (View != null && View.SelectedFiles.Any())
|
@if (View != null && View.SelectedFiles.Any())
|
||||||
{
|
{
|
||||||
<div class="fw-bold me-5">
|
<div class="fw-bold me-5">
|
||||||
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
|
<span class="me-2">
|
||||||
|
@(View.SelectedFiles.Length) <TL>selected</TL>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
||||||
@@ -58,37 +59,46 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
<div class="btn-group me-3">
|
||||||
<span class="svg-icon svg-icon-muted svg-icon-2">
|
<button type="button" @onclick="Launch" class="btn btn-light-primary">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<TL>Launch WinSCP</TL>
|
||||||
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
</button>
|
||||||
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
<button type="button" class="btn btn-light-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
</svg>
|
<span class="visually-hidden"></span>
|
||||||
</span>
|
</button>
|
||||||
<TL>Launch WinSCP</TL>
|
<ul class="dropdown-menu">
|
||||||
</button>
|
<li>
|
||||||
|
<a class="dropdown-item btn" target="_blank" href="https://winscp.net/eng/downloads.php">
|
||||||
|
<TL>Download WinSCP</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="dropdown-item btn" @onclick="() => ConnectionDetailsModal.Show()">
|
||||||
|
<TL>Show connection details</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" @onclick="CreateFile" class="btn btn-light-primary me-3">
|
<div class="btn-group me-3">
|
||||||
<span class="svg-icon svg-icon-2">
|
<button type="button" class="btn btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<TL>New</TL>
|
||||||
<path fill="currentColor" d="M6 22h12a2 2 0 0 0 2-2V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2zm7-18 5 5h-5V4zM8 14h3v-3h2v3h3v2h-3v3h-2v-3H8v-2z"></path>
|
</button>
|
||||||
</svg>
|
<ul class="dropdown-menu">
|
||||||
</span>
|
<li>
|
||||||
<TL>New file</TL>
|
<button @onclick="CreateFile" class="dropdown-item btn">
|
||||||
</button>
|
<TL>New file</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button @onclick="CreateFolder" class="dropdown-item btn">
|
||||||
|
<TL>New folder</TL>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged"/>
|
||||||
<span class="svg-icon svg-icon-2">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
|
||||||
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<TL>New folder</TL>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,6 +121,8 @@ else
|
|||||||
Access="MoveAccess"
|
Access="MoveAccess"
|
||||||
OnSubmit="OnFileMoveSubmit">
|
OnSubmit="OnFileMoveSubmit">
|
||||||
</FileSelectModal>
|
</FileSelectModal>
|
||||||
|
|
||||||
|
<ConnectionDetailsModal @ref="ConnectionDetailsModal" Access="Access"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
@@ -136,6 +148,9 @@ else
|
|||||||
// Config
|
// Config
|
||||||
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
||||||
|
|
||||||
|
// Connection details
|
||||||
|
private ConnectionDetailsModal ConnectionDetailsModal;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
MoveAccess = (FileAccess)Access.Clone();
|
MoveAccess = (FileAccess)Access.Clone();
|
||||||
@@ -152,7 +167,7 @@ else
|
|||||||
SmartTranslateService.Translate("Rename"),
|
SmartTranslateService.Translate("Rename"),
|
||||||
SmartTranslateService.Translate("Enter a new name"),
|
SmartTranslateService.Translate("Enter a new name"),
|
||||||
x.Name
|
x.Name
|
||||||
);
|
);
|
||||||
|
|
||||||
if (name != x.Name)
|
if (name != x.Name)
|
||||||
{
|
{
|
||||||
@@ -163,7 +178,7 @@ else
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.Add(new ()
|
actions.Add(new()
|
||||||
{
|
{
|
||||||
Id = "download",
|
Id = "download",
|
||||||
Name = "Download",
|
Name = "Download",
|
||||||
@@ -201,7 +216,7 @@ else
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.Add(new ()
|
actions.Add(new()
|
||||||
{
|
{
|
||||||
Id = "decompress",
|
Id = "decompress",
|
||||||
Name = "Decompress",
|
Name = "Decompress",
|
||||||
@@ -255,6 +270,13 @@ else
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void Save()
|
||||||
|
{
|
||||||
|
var data = await Editor.GetData();
|
||||||
|
await Access.Write(EditingFile, data);
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Successfully saved file"));
|
||||||
|
}
|
||||||
|
|
||||||
private async void Cancel(bool save = false)
|
private async void Cancel(bool save = false)
|
||||||
{
|
{
|
||||||
if (save)
|
if (save)
|
||||||
@@ -299,7 +321,7 @@ else
|
|||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await Access.Write(new FileData{IsFile = true, Name = name}, "");
|
await Access.Write(new FileData { IsFile = true, Name = name }, "");
|
||||||
await View!.Refresh();
|
await View!.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Logging.Net
|
|
||||||
|
|
||||||
<div class="badge badge-lg badge-light-primary">
|
<div class="badge badge-lg badge-light-primary">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Logging.Net
|
|
||||||
@using BlazorContextMenu
|
@using BlazorContextMenu
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@typeparam T
|
@typeparam T
|
||||||
@using Logging.Net
|
|
||||||
@inherits InputBase<T>
|
@inherits InputBase<T>
|
||||||
|
|
||||||
<div class="dropdown w-100">
|
<div class="dropdown w-100">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
|
||||||
|
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
|
||||||
|
|
||||||
@inject ReCaptchaService ReCaptchaService
|
@inject ReCaptchaService ReCaptchaService
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
<button class="btn @(CssClasses)" @onclick="Do">
|
<button class="btn @(CssClasses)" @onclick="Do">
|
||||||
@Text
|
@Text
|
||||||
|
@ChildContent
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -20,14 +21,17 @@ else
|
|||||||
public string CssClasses { get; set; } = "btn-primary";
|
public string CssClasses { get; set; } = "btn-primary";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } = "Mache was";
|
public string Text { get; set; } = "";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string WorkingText { get; set; } = "Verarbeite...";
|
public string WorkingText { get; set; } = "";
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<Task>? OnClick { get; set; }
|
public Func<Task>? OnClick { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
private async Task Do()
|
private async Task Do()
|
||||||
{
|
{
|
||||||
Working = true;
|
Working = true;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/auditlog">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/malware">
|
||||||
<TL>AuditLog</TL>
|
<TL>Malware</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<a class="text-gray-900 fs-2 fw-bold me-1">@(User.FirstName) @(User.LastName)</a>
|
<a class="text-gray-900 fs-2 fw-bold me-1 @(User.StreamerMode ? "blur" : "")">@(User.FirstName) @(User.LastName)</a>
|
||||||
|
|
||||||
@if (User.Status == UserStatus.Verified)
|
@if (User.Status == UserStatus.Verified)
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
||||||
<span class="d-flex align-items-center text-gray-400 mb-2">
|
<span class="d-flex align-items-center text-gray-400 mb-2 @(User.StreamerMode ? "blur" : "")">
|
||||||
@(User.Email)
|
@(User.Email)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,14 +69,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="fw-bold d-flex align-items-center fs-5">
|
<div class="fw-bold d-flex align-items-center fs-5">
|
||||||
@(User.FirstName) @(User.LastName)
|
<div class="@(User.StreamerMode ? "blur" : "")">
|
||||||
|
@(User.FirstName) @(User.LastName)
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (User.Admin)
|
@if (User.Admin)
|
||||||
{
|
{
|
||||||
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a class="fw-semibold text-muted text-hover-primary fs-7">@(User.Email)</a>
|
<a class="fw-semibold text-muted text-hover-primary fs-7 @(User.StreamerMode ? "blur" : "")">@(User.Email)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
<div class="app-sidebar-footer flex-column-auto pt-2 pb-6 px-6" id="kt_app_sidebar_footer">
|
<div class="app-sidebar-footer flex-column-auto pt-2 pb-6 px-6" id="kt_app_sidebar_footer">
|
||||||
<a href="/support" class="btn btn-flex flex-center btn-custom btn-primary overflow-hidden text-nowrap px-0 h-40px w-100 btn-label">
|
<a href="/support" class="btn btn-flex flex-center btn-custom btn-primary overflow-hidden text-nowrap px-0 h-40px w-100 btn-label">
|
||||||
<TL>Open support</TL>
|
<i class="bx bx-sm bx-support"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Rendering
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject IpBanService IpBanService
|
@inject IpBanService IpBanService
|
||||||
@inject DynamicBackgroundService DynamicBackgroundService
|
@inject DynamicBackgroundService DynamicBackgroundService
|
||||||
|
@inject KeyListenerService KeyListenerService
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var uri = new Uri(NavigationManager.Uri);
|
var uri = new Uri(NavigationManager.Uri);
|
||||||
@@ -207,10 +208,14 @@
|
|||||||
UserProcessed = true;
|
UserProcessed = true;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
|
try
|
||||||
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
|
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
|
||||||
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
|
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
|
||||||
|
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
|
||||||
|
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
|
||||||
|
}
|
||||||
|
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
|
||||||
|
|
||||||
await SessionService.Register();
|
await SessionService.Register();
|
||||||
|
|
||||||
@@ -236,6 +241,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await KeyListenerService.Initialize();
|
||||||
|
|
||||||
RunDelayedMenu(0);
|
RunDelayedMenu(0);
|
||||||
RunDelayedMenu(1);
|
RunDelayedMenu(1);
|
||||||
RunDelayedMenu(3);
|
RunDelayedMenu(3);
|
||||||
@@ -252,6 +259,8 @@
|
|||||||
{
|
{
|
||||||
SessionService.Close();
|
SessionService.Close();
|
||||||
|
|
||||||
|
await KeyListenerService.DisposeAsync();
|
||||||
|
|
||||||
if (User != null)
|
if (User != null)
|
||||||
{
|
{
|
||||||
await Event.Off($"supportChat.{User.Id}.message", this);
|
await Event.Off($"supportChat.{User.Id}.message", this);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Repositories.Domains
|
@using Moonlight.App.Repositories.Domains
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using Logging.Net
|
|
||||||
|
|
||||||
@inject ServerRepository ServerRepository
|
@inject ServerRepository ServerRepository
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
|
||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
@using Moonlight.App.ApiClients.Wings.Resources
|
@using Moonlight.App.ApiClients.Wings.Resources
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -131,7 +131,8 @@
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Debug(e.Message);
|
Logger.Verbose($"Error fetching status for node '{node.Name}'");
|
||||||
|
Logger.Verbose(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
@page "/admin/servers/cleanup"
|
@page "/admin/servers/cleanup"
|
||||||
|
|
||||||
@using Moonlight.App.Services
|
|
||||||
@using Moonlight.App.Models.Misc
|
|
||||||
@using Moonlight.App.Services.LogServices
|
|
||||||
@using Moonlight.App.Events
|
@using Moonlight.App.Events
|
||||||
@using Moonlight.App.Services.Background
|
@using Moonlight.App.Services.Background
|
||||||
|
|
||||||
@inject CleanupService CleanupService
|
@inject CleanupService CleanupService
|
||||||
@inject AuditLogService AuditLogService
|
|
||||||
@inject EventSystem Event
|
@inject EventSystem Event
|
||||||
|
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
@page "/admin/servers/images"
|
@page "/admin/servers/images"
|
||||||
|
|
||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
@using Logging.Net
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using System.Text
|
@using System.Text
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
|
|
||||||
@inject Repository<Image> ImageRepository
|
@inject Repository<Image> ImageRepository
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Models.Forms
|
@using Moonlight.App.Models.Forms
|
||||||
|
|
||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
|
|||||||
85
Moonlight/Shared/Views/Admin/Servers/View/Allocations.razor
Normal file
85
Moonlight/Shared/Views/Admin/Servers/View/Allocations.razor
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using BlazorTable
|
||||||
|
|
||||||
|
@inject Repository<Server> ServerRepository
|
||||||
|
@inject Repository<NodeAllocation> NodeAllocationRepository
|
||||||
|
@inject AlertService AlertService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 mb-5">
|
||||||
|
<div class="card card-body">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Add allocation"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Searching"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="AddAllocation">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
@foreach (var allocation in Server.Allocations)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle fs-5">
|
||||||
|
@(Server.Node.Fqdn + ":" + allocation.Port)
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="() => DeleteAllocation(allocation)">
|
||||||
|
</WButton>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public Server Server { get; set; }
|
||||||
|
|
||||||
|
private async Task AddAllocation()
|
||||||
|
{
|
||||||
|
// We have sadly no choice to use entity framework to do what the sql call does, there
|
||||||
|
// are only slower ways, so we will use a raw sql call as a exception
|
||||||
|
|
||||||
|
var freeAllocation = NodeAllocationRepository
|
||||||
|
.Get()
|
||||||
|
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={Server.Node.Id} LIMIT 1")
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (freeAllocation == null)
|
||||||
|
{
|
||||||
|
await AlertService.Error(
|
||||||
|
SmartTranslateService.Translate("No free allocation found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server.Allocations.Add(freeAllocation);
|
||||||
|
ServerRepository.Update(Server);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteAllocation(NodeAllocation nodeAllocation)
|
||||||
|
{
|
||||||
|
Server.Allocations.Remove(nodeAllocation);
|
||||||
|
ServerRepository.Update(Server);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
</Route>
|
</Route>
|
||||||
<Route Path="/allocations">
|
<Route Path="/allocations">
|
||||||
<AdminServersViewNavigation Index="3" Server="Server"/>
|
<AdminServersViewNavigation Index="3" Server="Server"/>
|
||||||
|
<Allocations />
|
||||||
</Route>
|
</Route>
|
||||||
<Route Path="/archive">
|
<Route Path="/archive">
|
||||||
<AdminServersViewNavigation Index="4" Server="Server"/>
|
<AdminServersViewNavigation Index="4" Server="Server"/>
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
.Include(x => x.Allocations)
|
.Include(x => x.Allocations)
|
||||||
.Include(x => x.MainAllocation)
|
.Include(x => x.MainAllocation)
|
||||||
.Include(x => x.Variables)
|
.Include(x => x.Variables)
|
||||||
|
.Include(x => x.Node)
|
||||||
.FirstOrDefault(x => x.Id == Id);
|
.FirstOrDefault(x => x.Id == Id);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
@@ -3,37 +3,18 @@
|
|||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.LogServices
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
|
||||||
@inject LogService LogService
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<AdminSystemNavigation Index="1"/>
|
<AdminSystemNavigation Index="1"/>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<LazyLoader Load="Load">
|
|
||||||
<Table TableItem="LogEntry" Items="LogEntries" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
|
||||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="false"></Column>
|
|
||||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log level"))" Field="@(x => x.Level)" Sortable="true" Filterable="false"></Column>
|
|
||||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log message"))" Field="@(x => x.Message)" Sortable="false" Filterable="true"></Column>
|
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
|
||||||
</Table>
|
|
||||||
</LazyLoader>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private LogEntry[] LogEntries;
|
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
private Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
LogEntries = LogService.GetMessages();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
134
Moonlight/Shared/Views/Admin/Sys/Malware.razor
Normal file
134
Moonlight/Shared/Views/Admin/Sys/Malware.razor
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
@page "/admin/system/malware"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Services.Background
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using BlazorTable
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Events
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
|
||||||
|
@inject MalwareScanService MalwareScanService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject EventSystem Event
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminSystemNavigation Index="2"/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
@if (MalwareScanService.IsRunning)
|
||||||
|
{
|
||||||
|
<span class="fs-3 spinner-border align-middle me-3"></span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="fs-3">@(MalwareScanService.Status)</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
@if (MalwareScanService.IsRunning)
|
||||||
|
{
|
||||||
|
<button class="btn btn-success disabled">
|
||||||
|
<TL>Scan in progress</TL>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
|
||||||
|
CssClasses="btn-success"
|
||||||
|
OnClick="MalwareScanService.Start">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Results</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="Server" Items="ScanResults.Keys" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<a href="/server/@(context.Uuid)">@(context.Name)</a>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Results"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
|
<Template>
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var result in ScanResults[context])
|
||||||
|
{
|
||||||
|
<div class="col-12 col-md-6 p-3">
|
||||||
|
<div class="accordion" id="scanResult@(result.GetHashCode())">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="scanResult-header@(result.GetHashCode())">
|
||||||
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scanResult-body@(result.GetHashCode())" aria-expanded="false" aria-controls="scanResult-body@(result.GetHashCode())">
|
||||||
|
<span>@(result.Title)</span>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="scanResult-body@(result.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="scanResult-header@(result.GetHashCode())" data-bs-parent="#scanResult">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>
|
||||||
|
@(result.Description)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Server, MalwareScanResult[]> ScanResults = new();
|
||||||
|
|
||||||
|
private LazyLoader LazyLoaderResults;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
|
||||||
|
|
||||||
|
await Event.On<Object>("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LoadResults(LazyLoader arg)
|
||||||
|
{
|
||||||
|
ScanResults.Clear();
|
||||||
|
|
||||||
|
lock (MalwareScanService.ScanResults)
|
||||||
|
{
|
||||||
|
foreach (var result in MalwareScanService.ScanResults)
|
||||||
|
{
|
||||||
|
ScanResults.Add(result.Key, result.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Dispose()
|
||||||
|
{
|
||||||
|
await Event.Off("malwareScan.status", this);
|
||||||
|
await Event.Off("malwareScan.result", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
@using Moonlight.App.Models.Forms
|
@using Moonlight.App.Models.Forms
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Mappy.Net
|
@using Mappy.Net
|
||||||
|
@using Moonlight.App.Exceptions
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
@inject UserRepository UserRepository
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@
|
|||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<SmartForm OnValidSubmit="Save" Model="Model">
|
<SmartForm OnValidSubmit="Save" Model="Model">
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="card mb-5 mb-xl-10">
|
||||||
<div class="card-body p-9">
|
<div class="card-body p-9 @(CurrentUser.StreamerMode ? "blur" : "")">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -74,7 +76,7 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private UserDataModel Model = new UserDataModel();
|
private UserDataModel Model = new();
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public User CurrentUser { get; set; }
|
public User CurrentUser { get; set; }
|
||||||
@@ -89,9 +91,20 @@
|
|||||||
|
|
||||||
private Task Save()
|
private Task Save()
|
||||||
{
|
{
|
||||||
CurrentUser = Mapper.Map(CurrentUser, Model);
|
// Prevent users from locking out other users by changing their email
|
||||||
|
|
||||||
CurrentUser.Email = CurrentUser.Email.ToLower();
|
Model.Email = Model.Email.ToLower();
|
||||||
|
var userWithThatEmail = UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Email == Model.Email);
|
||||||
|
|
||||||
|
if (userWithThatEmail != null && CurrentUser.Id != userWithThatEmail.Id)
|
||||||
|
{
|
||||||
|
Logger.Warn($"A user tried to lock another user out by changing the email. Email: {Model.Email}", "security");
|
||||||
|
throw new DisplayException("A user with that email does already exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentUser = Mapper.Map(CurrentUser, Model);
|
||||||
|
|
||||||
UserRepository.Update(CurrentUser);
|
UserRepository.Update(CurrentUser);
|
||||||
|
|
||||||
|
|||||||
@@ -2,319 +2,232 @@
|
|||||||
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using QRCoder
|
@using QRCoder
|
||||||
@using Moonlight.App.Services.LogServices
|
|
||||||
@using Moonlight.App.Services.Sessions
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using System.Text.RegularExpressions
|
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Models.Misc
|
@using Mappy.Net
|
||||||
|
@using Moonlight.App.Models.Forms
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
@inject AuditLogService AuditLogService
|
|
||||||
@inject TotpService TotpService
|
@inject TotpService TotpService
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IdentityService IdentityService
|
|
||||||
@inject UserService UserService
|
@inject UserService UserService
|
||||||
@inject AlertService AlertService
|
@inject NavigationManager NavigationManager
|
||||||
@inject ToastService ToastService
|
@inject ModalService ModalService
|
||||||
|
@inject Repository<User> UserRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<ProfileNavigation Index="2"/>
|
<ProfileNavigation Index="2"/>
|
||||||
|
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="row">
|
||||||
<LazyLoader Load="Load">
|
<div class="col-12 col-md-6 p-3">
|
||||||
@if (TotpEnabled)
|
<div class="card">
|
||||||
{
|
<div class="card-header">
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
<div class="card-title">
|
||||||
<table class="w-100">
|
<TL>Two factor authentication</TL>
|
||||||
<tr>
|
</div>
|
||||||
<td rowspan="2">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-100">
|
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
|
||||||
<TL>Your account is secured with 2fa</TL>
|
|
||||||
</h4>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<a @onclick="Disable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
|
||||||
<TL>Disable</TL>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>anyone write a fancy text here?</TL>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="card-body fs-6">
|
||||||
else
|
<p>
|
||||||
{
|
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
</p>
|
||||||
<table class="w-100">
|
<div class="d-flex justify-content-center">
|
||||||
<tr>
|
@if (User.TotpEnabled)
|
||||||
<td rowspan="2">
|
{
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
<WButton Text="@(SmartTranslateService.Translate("Disable"))"
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
WorkingText=""
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
CssClasses="btn-danger"
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
OnClick="DisableTwoFactor">
|
||||||
</svg>
|
</WButton>
|
||||||
</span>
|
}
|
||||||
</td>
|
else
|
||||||
<td class="w-100">
|
{
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
<TL>Secure your account</TL>
|
WorkingText=""
|
||||||
</h4>
|
CssClasses="btn-primary"
|
||||||
</td>
|
OnClick="StartTwoFactorWizard">
|
||||||
<td rowspan="2">
|
</WButton>
|
||||||
<a @onclick="Enable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
}
|
||||||
<TL>Enable</TL>
|
</div>
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" id="twofactorauth" tabindex="-1" style="display: none;" aria-hidden="true">
|
<div class="col-12 col-md-6 p-3">
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-header">
|
||||||
<div class="modal-header flex-stack py-6">
|
<div class="card-title">
|
||||||
<h2 class="ms-3">
|
<TL>Password</TL>
|
||||||
<TL>Activate 2fa</TL>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
<div class="card-body fs-6">
|
||||||
<span class="svg-icon svg-icon-1">
|
<div class="d-flex justify-content-center">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div class="input-group">
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
<input @bind="Password" class="form-control" type="password"/>
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
</svg>
|
WorkingText=""
|
||||||
</span>
|
CssClasses="btn-primary"
|
||||||
</div>
|
OnClick="ChangePassword">
|
||||||
</div>
|
</WButton>
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-dark fw-bold mb-3 mt-2">
|
|
||||||
<TL>2fa apps</TL>
|
|
||||||
</h3>
|
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
|
||||||
<TL>Use an app like </TL>
|
|
||||||
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>,
|
|
||||||
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>,
|
|
||||||
<a href="https://authy.com/download/" target="_blank">Authy</a>, <TL>or</TL>
|
|
||||||
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a> <TL>and scan the following QR Code</TL>
|
|
||||||
@if (EnablingTotp)
|
|
||||||
{
|
|
||||||
<div class="pt-5 text-center">
|
|
||||||
@{
|
|
||||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
|
||||||
|
|
||||||
var qrCodeData = qrGenerator.CreateQrCode
|
|
||||||
(
|
|
||||||
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={TotpSecret}&issuer={Uri.EscapeDataString(Issuer)}",
|
|
||||||
QRCodeGenerator.ECCLevel.Q
|
|
||||||
);
|
|
||||||
|
|
||||||
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
|
|
||||||
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
|
|
||||||
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
|
|
||||||
}
|
|
||||||
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed mb-8 p-6">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-warning me-4">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.3" x="2" y="2" width="20" height="20" rx="10" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="14" width="7" height="2" rx="1" transform="rotate(-90 11 14)" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="17" width="2" height="2" rx="1" transform="rotate(-90 11 17)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div class="d-flex flex-stack flex-grow-1">
|
|
||||||
<div class="fw-semibold">
|
|
||||||
<div class="fs-6 text-gray-700">
|
|
||||||
<TL>If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:</TL>
|
|
||||||
<div class="fw-bold text-dark pt-2">@(TotpSecret)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-primary px-6 align-self-center text-nowrap float-end" data-bs-toggle="modal" data-bs-target="#test">
|
|
||||||
<TL>Next</TL>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="modal fade" id="test" tabindex="-1" style="display: none;" aria-hidden="true">
|
<div class="col-12 col-md-6 p-3">
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
<div class="card">
|
||||||
<div class="modal-content">
|
<div class="card-header">
|
||||||
<div class="modal-header flex-stack py-6">
|
<div class="card-title">
|
||||||
<h2 class="ms-3">
|
<TL>Preferences</TL>
|
||||||
<TL>Finish activation</TL>
|
</div>
|
||||||
</h2>
|
</div>
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
<div class="card-body fs-6">
|
||||||
<span class="svg-icon svg-icon-1">
|
<div class="form-check form-switch">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<input @bind="UserModel.StreamerMode" class="form-check-input" type="checkbox" role="switch" id="streamerModeSwitch">
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
<label class="form-check-label" for="streamerModeSwitch">
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
<TL>Streamer mode</TL>
|
||||||
</svg>
|
</label>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-footer">
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
<div class="text-end">
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
<div class="alert alert-primary d-flex align-items-center p-5 mb-6">
|
WorkingText=""
|
||||||
<i class="bx bx-info-circle fs-2hx text-primary me-4">
|
CssClasses="btn-primary"
|
||||||
<span class="path1"></span><span class="path2"></span>
|
OnClick="SavePreferences">
|
||||||
</i>
|
</WButton>
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<h4 class="mb-1 text-primary">
|
|
||||||
<TL>2fa Code requiered</TL>
|
|
||||||
</h4>
|
|
||||||
<span>In order to finish the activation of 2fa, you need to enter the code your 2fa app shows you.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control form-control-lg form-control-solid mb-0" placeholder="@SmartTranslateService.Translate("2fa Code")" @bind="currentTotp"/>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<WButton CssClasses="btn btn-primary mb-2 align-self-center text-nowrap float-end" WorkingText="@SmartTranslateService.Translate("Saving")" Text="@SmartTranslateService.Translate("Finish")" OnClick="CheckAndSaveTotp">
|
|
||||||
</WButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Modals *@
|
||||||
|
<div class="modal fade" id="2fa" tabindex="-1" style="display: none" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">
|
||||||
|
<TL>Activate 2fa</TL>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body fs-6">
|
||||||
|
@if (!User.TotpEnabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(User.TotpSecret))
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<TL>Make sure you have installed one of the following apps on your smartphone and press continue</TL>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://authy.com/download/" target="_blank">Authy</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
<div class="separator mt-2"></div>
|
<div class="d-flex justify-content-center">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Continue"))"
|
||||||
<div class="alert alert-danger d-flex rounded ms-6 me-6 mt-6 mb-8 bg-body">
|
WorkingText="@(SmartTranslateService.Translate("Preparing"))"
|
||||||
<div class="w-100">
|
CssClasses="btn-primary"
|
||||||
<table>
|
OnClick="GenerateTwoFactorToken">
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-body">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-25">
|
|
||||||
<span class="text-gray-700 fw-semibold fs-6 ms-4 me-2">
|
|
||||||
<TL>New password</TL>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-75">
|
|
||||||
<input @bind="Password" type="password" class="form-control">
|
|
||||||
</td>
|
|
||||||
<td class="">
|
|
||||||
<WButton OnClick="ChangePassword"
|
|
||||||
CssClasses="btn-danger ms-4"
|
|
||||||
Text="@SmartTranslateService.Translate("Change")"
|
|
||||||
WorkingText="@SmartTranslateService.Translate("Changing")">
|
|
||||||
</WButton>
|
</WButton>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
}
|
||||||
</table>
|
else
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<TL>Scan the qr code and enter the code generated by the app you have scanned it in</TL>
|
||||||
|
</p>
|
||||||
|
<div class="mt-3 text-center">
|
||||||
|
@{
|
||||||
|
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||||
|
|
||||||
|
var qrCodeData = qrGenerator.CreateQrCode
|
||||||
|
(
|
||||||
|
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={User.TotpSecret}&issuer={Uri.EscapeDataString("Moonlight")}",
|
||||||
|
QRCodeGenerator.ECCLevel.Q
|
||||||
|
);
|
||||||
|
|
||||||
|
PngByteQRCode qrCode = new PngByteQRCode(qrCodeData);
|
||||||
|
byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20);
|
||||||
|
var base64 = Convert.ToBase64String(qrCodeAsPngByteArr);
|
||||||
|
}
|
||||||
|
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 d-flex justify-content-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text"
|
||||||
|
@bind="TwoFactorCode"
|
||||||
|
placeholder="@(SmartTranslateService.Translate("Enter your 2fa code here"))"
|
||||||
|
class="form-control"/>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="EnableTwoFactor">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private bool TotpEnabled = false;
|
[CascadingParameter]
|
||||||
private bool EnablingTotp = false;
|
public User User { get; set; }
|
||||||
private string TotpSecret = "";
|
|
||||||
private User User;
|
|
||||||
private string Issuer = "Moonlight";
|
|
||||||
private string currentTotp = "";
|
|
||||||
|
|
||||||
|
private string TwoFactorCode = "";
|
||||||
private string Password = "";
|
private string Password = "";
|
||||||
|
private UserPreferencesDataModel UserModel;
|
||||||
|
|
||||||
private async void Enable()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.EnableTotp, x => x.Add<string>("Totp enabled"));
|
UserModel = Mapper.Map<UserPreferencesDataModel>(User);
|
||||||
await TotpService.Enable();
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
EnablingTotp = true;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckAndSaveTotp()
|
private async Task StartTwoFactorWizard()
|
||||||
{
|
{
|
||||||
if (await TotpService.Verify(TotpSecret, currentTotp))
|
await ModalService.Show("2fa");
|
||||||
{
|
|
||||||
await TotpService.EnforceTotpLogin();
|
|
||||||
TotpEnabled = true;
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
await ToastService.Success("Successfully enabled 2fa!");
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await AlertService.Error("2fa code incorrect", "The given 2fa code is incorrect. Maybe check if the code in your 2fa app has changed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Disable()
|
private async Task GenerateTwoFactorToken()
|
||||||
{
|
{
|
||||||
await AuditLogService.Log(AuditLogType.DisableTotp, x => x.Add<string>("Totp disabled"));
|
await TotpService.GenerateSecret();
|
||||||
await TotpService.Disable();
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnableTwoFactor()
|
||||||
|
{
|
||||||
|
await ModalService.Hide("2fa");
|
||||||
|
await TotpService.Enable(TwoFactorCode);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private async Task DisableTwoFactor()
|
||||||
{
|
{
|
||||||
await lazyLoader.SetText("Requesting secrets");
|
await TotpService.Disable();
|
||||||
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
|
|
||||||
await lazyLoader.SetText("Requesting identity");
|
|
||||||
User = await IdentityService.Get();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangePassword()
|
private async Task ChangePassword()
|
||||||
{
|
{
|
||||||
if (Regex.IsMatch(Password, @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z@$!%*#.,?&\d]{8,}$"))
|
await UserService.ChangePassword(User, Password);
|
||||||
{
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
await UserService.ChangePassword(User, Password);
|
}
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.PasswordChange, x => x.Add<string>("The password has been set to a new one"));
|
private async Task SavePreferences()
|
||||||
|
{
|
||||||
// Reload to make the user login again
|
User = Mapper.Map(User, UserModel);
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
UserRepository.Update(User);
|
||||||
}
|
await InvokeAsync(StateHasChanged);
|
||||||
else
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
{
|
|
||||||
await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
@using Task = System.Threading.Tasks.Task
|
@using Task = System.Threading.Tasks.Task
|
||||||
@using Moonlight.App.Repositories.Servers
|
@using Moonlight.App.Repositories.Servers
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Logging.Net
|
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Events
|
@using Moonlight.App.Events
|
||||||
@using Moonlight.App.Helpers.Wings
|
@using Moonlight.App.Helpers.Wings
|
||||||
@@ -27,19 +26,7 @@
|
|||||||
<LazyLoader Load="LoadData">
|
<LazyLoader Load="LoadData">
|
||||||
@if (CurrentServer == null)
|
@if (CurrentServer == null)
|
||||||
{
|
{
|
||||||
<div class="d-flex justify-content-center flex-center">
|
<NotFoundAlert />
|
||||||
<div class="card">
|
|
||||||
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<h1 class="card-title">
|
|
||||||
<TL>Server not found</TL>
|
|
||||||
</h1>
|
|
||||||
<p class="card-text fs-4">
|
|
||||||
<TL>A server with that id cannot be found or you have no access for this server</TL>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -305,10 +292,6 @@
|
|||||||
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
|
await DynamicBackgroundService.Change(Image.BackgroundImageUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Debug("Server is null");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReconnectConsole()
|
private async Task ReconnectConsole()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Addon
|
@using Moonlight.App.Services.Addon
|
||||||
@using Moonlight.App.ApiClients.Modrinth.Resources
|
@using Moonlight.App.ApiClients.Modrinth.Resources
|
||||||
@using Logging.Net
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
@inject ServerAddonPluginService AddonPluginService
|
@inject ServerAddonPluginService AddonPluginService
|
||||||
@@ -33,14 +33,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<LazyLoader @ref="PluginsLazyLoader" Load="LoadPlugins">
|
<LazyLoader @ref="PluginsLazyLoader" Load="LoadPlugins">
|
||||||
@foreach (var pluginsPart in Plugins.Chunk(3))
|
@if (Plugins.Any())
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@foreach (var plugin in pluginsPart)
|
@foreach (var plugin in Plugins)
|
||||||
{
|
{
|
||||||
<div class="col">
|
<div class="col-12 col-md-4 p-3">
|
||||||
<div class="card p-2 card-bordered border-active">
|
<div class="card bg-hover-secondary position-relative">
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center pt-5">
|
||||||
<img height="100" width="100" src="@(plugin.IconUrl)" alt="@(plugin.Title)"/>
|
<img height="100" width="100" src="@(plugin.IconUrl)" alt="@(plugin.Title)"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -48,10 +48,14 @@
|
|||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
@(plugin.Description)
|
@(plugin.Description)
|
||||||
</p>
|
</p>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Install"))"
|
</div>
|
||||||
WorkingText="@(SmartTranslateService.Translate("Installing"))"
|
<div class="position-absolute top-0 end-0 mt-3 me-3">
|
||||||
CssClasses="btn-primary"
|
<a href="https://modrinth.com/plugin/@(plugin.Slug)" class="btn btn-primary p-2" target="_blank">
|
||||||
|
<i class="ps-1 bx bx-sm bx-link"></i>
|
||||||
|
</a>
|
||||||
|
<WButton CssClasses="btn-success p-2"
|
||||||
OnClick="() => InstallPlugin(plugin)">
|
OnClick="() => InstallPlugin(plugin)">
|
||||||
|
<i class="ps-1 bx bx-sm bx-download"></i>
|
||||||
</WButton>
|
</WButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,6 +63,10 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<NotFoundAlert />
|
||||||
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,13 +139,7 @@ else
|
|||||||
|
|
||||||
await ToastService.CreateProcessToast("pluginDownload", "Preparing");
|
await ToastService.CreateProcessToast("pluginDownload", "Preparing");
|
||||||
|
|
||||||
await AddonPluginService.InstallPlugin(fileAccess, version, project, delegate(string s)
|
await AddonPluginService.InstallPlugin(fileAccess, version, project, delegate(string s) { Task.Run(async () => { await ToastService.UpdateProcessToast("pluginDownload", s); }); });
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await ToastService.UpdateProcessToast("pluginDownload", s);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await ToastService.Success(
|
await ToastService.Success(
|
||||||
SmartTranslateService.Translate("Successfully installed " + project.Slug)
|
SmartTranslateService.Translate("Successfully installed " + project.Slug)
|
||||||
@@ -145,7 +147,8 @@ else
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Info(e.Message);
|
Logger.Warn("Error installing plugin");
|
||||||
|
Logger.Warn(e);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -155,4 +158,3 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Logging.Net
|
|
||||||
@using BlazorContextMenu
|
@using BlazorContextMenu
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Events
|
@using Moonlight.App.Events
|
||||||
|
|||||||
@@ -42,15 +42,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row mt-3">
|
||||||
<div class="separator my-5"></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
||||||
<span class="ms-1 text-muted">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
<span class="ms-1 text-muted @(User.StreamerMode ? "blur" : "")">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||||
@@ -107,8 +104,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 row">
|
<div class="mt-5 row">
|
||||||
<div class="d-flex flex-column flex-md-row card card-body p-10">
|
<div class="d-flex flex-column flex-md-row card card-body p-5">
|
||||||
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column me-5 mb-3 mb-md-0 fs-6 min-w-lg-200px">
|
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column fs-6 pe-5 mb-5">
|
||||||
<li class="nav-item w-100 me-0 mb-md-2">
|
<li class="nav-item w-100 me-0 mb-md-2">
|
||||||
<a href="/server/@(CurrentServer.Uuid)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
|
<a href="/server/@(CurrentServer.Uuid)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
|
||||||
<i class="bx bx-terminal bx-sm me-2"></i>
|
<i class="bx bx-terminal bx-sm me-2"></i>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user