Added audit logging. Added admin view for audit log

This commit is contained in:
Marcel Baumgartner
2023-03-06 02:21:23 +01:00
parent d7b10aa224
commit 62cd63f56b
39 changed files with 2754 additions and 153 deletions

View File

@@ -9,4 +9,5 @@ public class AuditLogEntry
public string JsonData { get; set; } = ""; public string JsonData { get; set; } = "";
public bool System { get; set; } public bool System { get; set; }
public string Ip { get; set; } = ""; public string Ip { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
} }

View File

@@ -8,4 +8,5 @@ public class ErrorLogEntry
public string JsonData { get; set; } = ""; public string JsonData { get; set; } = "";
public string Ip { get; set; } = ""; public string Ip { get; set; } = "";
public string Class { get; set; } = ""; public string Class { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
} }

View File

@@ -9,4 +9,5 @@ public class SecurityLogEntry
public string Ip { get; set; } = ""; public string Ip { get; set; } = "";
public SecurityLogType Type { get; set; } public SecurityLogType Type { get; set; }
public string JsonData { get; set; } = ""; public string JsonData { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
} }

View File

@@ -0,0 +1,925 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Moonlight.App.Database;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20230305014834_AddedLoggingAndDbStuff")]
partial class AddedLoggingAndDbStuff
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("AaPanelId")
.HasColumnType("int");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Default")
.HasColumnType("tinyint(1)");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("DockerImages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("SharedDomainId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("SharedDomainId");
b.ToTable("Domains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallEntrypoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Startup")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StartupDetection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageTags");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("LoadingMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("HttpPort")
.HasColumnType("int");
b.Property<int>("MoonlightDaemonPort")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SftpPort")
.HasColumnType("int");
b.Property<bool>("Ssl")
.HasColumnType("tinyint(1)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Nodes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("NodeId")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("NodeAllocations");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NotificationClientId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NotificationClientId");
b.ToTable("NotificationActions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationClients");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Revokes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<long>("Disk")
.HasColumnType("bigint");
b.Property<int>("DockerImageIndex")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<bool>("Installing")
.HasColumnType("tinyint(1)");
b.Property<int>("MainAllocationId")
.HasColumnType("int");
b.Property<long>("Memory")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<string>("OverrideStartup")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<bool>("Suspended")
.HasColumnType("tinyint(1)");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("MainAllocationId");
b.HasIndex("NodeId");
b.HasIndex("OwnerId");
b.ToTable("Servers");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Bytes")
.HasColumnType("bigint");
b.Property<bool>("Created")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerBackups");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CloudflareId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("SharedDomains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Duration")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SellPassId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Amount")
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<int>("Disk")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<int>("Memory")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("SubscriptionId");
b.ToTable("SubscriptionLimits");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Answer")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsQuestion")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSupport")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSystem")
.HasColumnType("tinyint(1)");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("RecipientId")
.HasColumnType("int");
b.Property<int?>("SenderId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("SenderId");
b.ToTable("SupportMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Admin")
.HasColumnType("tinyint(1)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DiscordDiscriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
b.Property<string>("DiscordUsername")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("SubscriptionDuration")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.Property<DateTime?>("SubscriptionSince")
.HasColumnType("datetime(6)");
b.Property<bool>("SupportPending")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("TokenValidTime")
.HasColumnType("datetime(6)");
b.Property<bool>("TotpEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("TotpSecret")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("SubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("DockerImages")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain")
.WithMany()
.HasForeignKey("SharedDomainId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("SharedDomain");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Tags")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Variables")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Node", null)
.WithMany("Allocations")
.HasForeignKey("NodeId");
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Allocations")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient")
.WithMany()
.HasForeignKey("NotificationClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("NotificationClient");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation")
.WithMany()
.HasForeignKey("MainAllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
.WithMany()
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Image");
b.Navigation("MainAllocation");
b.Navigation("Node");
b.Navigation("Owner");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Variables")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Subscription", null)
.WithMany("Limits")
.HasForeignKey("SubscriptionId");
b.Navigation("Image");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId");
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
.WithMany()
.HasForeignKey("SenderId");
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Subscription", "Subscription")
.WithMany()
.HasForeignKey("SubscriptionId");
b.Navigation("Subscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Navigation("DockerImages");
b.Navigation("Tags");
b.Navigation("Variables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Variables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Navigation("Limits");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,66 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedLoggingAndDbStuff : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ErrorLog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Stacktrace = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
JsonData = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Ip = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Class = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_ErrorLog", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "SecurityLog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
Ip = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Type = table.Column<int>(type: "int", nullable: false),
JsonData = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_SecurityLog", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ErrorLog");
migrationBuilder.DropTable(
name: "SecurityLog");
}
}
}

View File

@@ -0,0 +1,934 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Moonlight.App.Database;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20230305021844_AddedDatesToLogs")]
partial class AddedDatesToLogs
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("AaPanelId")
.HasColumnType("int");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Default")
.HasColumnType("tinyint(1)");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("DockerImages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("SharedDomainId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("SharedDomainId");
b.ToTable("Domains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallEntrypoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Startup")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StartupDetection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageTags");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("LoadingMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("HttpPort")
.HasColumnType("int");
b.Property<int>("MoonlightDaemonPort")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SftpPort")
.HasColumnType("int");
b.Property<bool>("Ssl")
.HasColumnType("tinyint(1)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Nodes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("NodeId")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NodeId");
b.HasIndex("ServerId");
b.ToTable("NodeAllocations");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NotificationClientId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NotificationClientId");
b.ToTable("NotificationActions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationClients");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Revokes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<long>("Disk")
.HasColumnType("bigint");
b.Property<int>("DockerImageIndex")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<bool>("Installing")
.HasColumnType("tinyint(1)");
b.Property<int>("MainAllocationId")
.HasColumnType("int");
b.Property<long>("Memory")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<string>("OverrideStartup")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<bool>("Suspended")
.HasColumnType("tinyint(1)");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("MainAllocationId");
b.HasIndex("NodeId");
b.HasIndex("OwnerId");
b.ToTable("Servers");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Bytes")
.HasColumnType("bigint");
b.Property<bool>("Created")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerBackups");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CloudflareId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("SharedDomains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Duration")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SellPassId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Amount")
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<int>("Disk")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<int>("Memory")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("SubscriptionId");
b.ToTable("SubscriptionLimits");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Answer")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsQuestion")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSupport")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSystem")
.HasColumnType("tinyint(1)");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("RecipientId")
.HasColumnType("int");
b.Property<int?>("SenderId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("SenderId");
b.ToTable("SupportMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Admin")
.HasColumnType("tinyint(1)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DiscordDiscriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
b.Property<string>("DiscordUsername")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("SubscriptionDuration")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.Property<DateTime?>("SubscriptionSince")
.HasColumnType("datetime(6)");
b.Property<bool>("SupportPending")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("TokenValidTime")
.HasColumnType("datetime(6)");
b.Property<bool>("TotpEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("TotpSecret")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("SubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("DockerImages")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain")
.WithMany()
.HasForeignKey("SharedDomainId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("SharedDomain");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Tags")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Variables")
.HasForeignKey("ImageId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Node", null)
.WithMany("Allocations")
.HasForeignKey("NodeId");
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Allocations")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient")
.WithMany()
.HasForeignKey("NotificationClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("NotificationClient");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation")
.WithMany()
.HasForeignKey("MainAllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
.WithMany()
.HasForeignKey("NodeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Image");
b.Navigation("MainAllocation");
b.Navigation("Node");
b.Navigation("Owner");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Backups")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Server", null)
.WithMany("Variables")
.HasForeignKey("ServerId");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Subscription", null)
.WithMany("Limits")
.HasForeignKey("SubscriptionId");
b.Navigation("Image");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId");
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
.WithMany()
.HasForeignKey("SenderId");
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Subscription", "Subscription")
.WithMany()
.HasForeignKey("SubscriptionId");
b.Navigation("Subscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Navigation("DockerImages");
b.Navigation("Tags");
b.Navigation("Variables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Navigation("Allocations");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Navigation("Allocations");
b.Navigation("Backups");
b.Navigation("Variables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Navigation("Limits");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedDatesToLogs : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "SecurityLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "ErrorLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "AuditLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "SecurityLog");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "ErrorLog");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "AuditLog");
}
}
}

View File

@@ -19,31 +19,6 @@ namespace Moonlight.App.Database.Migrations
.HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -220,6 +195,95 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("LoadingMessages"); b.ToTable("LoadingMessages");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View File

@@ -76,4 +76,16 @@ public static class Formatter
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year} {i2s(e.Hour)}:{i2s(e.Minute)}"; return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year} {i2s(e.Hour)}:{i2s(e.Minute)}";
} }
public static string FormatDateOnly(DateTime e)
{
string i2s(int i)
{
if (i.ToString().Length < 2)
return "0" + i;
return i.ToString();
}
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year}";
}
} }

View File

@@ -67,7 +67,7 @@ public class WingsServerConverter
// Settings // Settings
wingsServer.Settings.Skip_Egg_Scripts = false; wingsServer.Settings.Skip_Egg_Scripts = false;
wingsServer.Settings.Suspended = false; //TODO: Implement wingsServer.Settings.Suspended = server.Suspended;
wingsServer.Settings.Invocation = string.IsNullOrEmpty(server.OverrideStartup) ? image.Startup : server.OverrideStartup; wingsServer.Settings.Invocation = string.IsNullOrEmpty(server.OverrideStartup) ? image.Startup : server.OverrideStartup;
wingsServer.Settings.Uuid = server.Uuid; wingsServer.Settings.Uuid = server.Uuid;

View File

@@ -27,7 +27,7 @@ public class PullController : Controller
Stream req = Request.Body; Stream req = Request.Body;
string jwt = await new StreamReader(req).ReadToEndAsync(); string jwt = await new StreamReader(req).ReadToEndAsync();
var dict = OneTimeJwtService.Validate(jwt); var dict = await OneTimeJwtService.Validate(jwt);
if (dict == null) if (dict == null)
return NotFound(); return NotFound();

View File

@@ -1,5 +1,7 @@
using Logging.Net; using Logging.Net;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Models.Misc;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Http.Controllers.Api.Moonlight; namespace Moonlight.App.Http.Controllers.Api.Moonlight;
@@ -7,12 +9,19 @@ 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;
public ResourcesController(SecurityLogService securityLogService)
{
SecurityLogService = securityLogService;
}
[HttpGet("images/{name}")] [HttpGet("images/{name}")]
public ActionResult GetImage([FromRoute] string name) public async Task<ActionResult> GetImage([FromRoute] string name)
{ {
if (name.Contains("..")) if (name.Contains(".."))
{ {
//TODO: Add security warn await SecurityLogService.Log(SecurityLogType.PathTransversal, name);
return NotFound(); return NotFound();
} }

View File

@@ -4,5 +4,19 @@ public enum AuditLogType
{ {
Login, Login,
Register, Register,
LoginFail ChangePassword,
ChangePowerState,
CreateBackup,
RestoreBackup,
DeleteBackup,
DownloadBackup,
CreateServer,
ReinstallServer,
CancelSubscription,
ApplySubscriptionCode,
EnableTotp,
DisableTotp,
AddDomainRecord,
UpdateDomainRecord,
DeleteDomainRecord
} }

View File

@@ -2,5 +2,8 @@
public enum SecurityLogType public enum SecurityLogType
{ {
ManipulatedJwt ManipulatedJwt,
PathTransversal,
SftpBruteForce,
LoginFail
} }

View File

@@ -9,7 +9,9 @@ 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.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;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -19,14 +21,18 @@ 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(ConfigService configService, public DomainService(
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")
@@ -46,10 +52,10 @@ public class DomainService
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
{ {
var domains = GetData( var domains = GetData(
await Client.Zones.GetAsync(new() await Client.Zones.GetAsync(new()
{ {
AccountId = AccountId AccountId = AccountId
}) })
); );
var sharedDomains = SharedDomainRepository.Get().ToArray(); var sharedDomains = SharedDomainRepository.Get().ToArray();
@@ -82,7 +88,7 @@ public class DomainService
{ {
if (record.Name.EndsWith(dname)) if (record.Name.EndsWith(dname))
{ {
result.Add(new () result.Add(new()
{ {
Name = record.Name.Replace(dname, ""), Name = record.Name.Replace(dname, ""),
Content = record.Content, Content = record.Content,
@@ -95,7 +101,7 @@ public class DomainService
} }
else if (record.Name.EndsWith(rname)) else if (record.Name.EndsWith(rname))
{ {
result.Add(new () result.Add(new()
{ {
Name = record.Name.Replace(rname, ""), Name = record.Name.Replace(rname, ""),
Content = record.Content, Content = record.Content,
@@ -107,14 +113,14 @@ public class DomainService
}); });
} }
} }
return result.ToArray(); return result.ToArray();
} }
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord) public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
{ {
var domain = EnsureData(d); var domain = EnsureData(d);
var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}"; var dname = $".{rname}";
@@ -134,7 +140,6 @@ public class DomainService
Type = dnsRecord.Type, Type = dnsRecord.Type,
Data = new() Data = new()
{ {
Service = parts[0], Service = parts[0],
Protocol = protocol, Protocol = protocol,
Name = name, Name = name,
@@ -146,7 +151,7 @@ public class DomainService
Proxied = dnsRecord.Proxied, Proxied = dnsRecord.Proxied,
Ttl = dnsRecord.Ttl, Ttl = dnsRecord.Ttl,
}; };
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv)); GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv));
} }
else else
@@ -163,32 +168,38 @@ public class DomainService
Name = name Name = name
})); }));
} }
await AuditLogService.Log(AuditLogType.AddDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
} }
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord) public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
{ {
var domain = EnsureData(d); var domain = EnsureData(d);
var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}"; var dname = $".{rname}";
if (dnsRecord.Type == DnsRecordType.Srv) if (dnsRecord.Type == DnsRecordType.Srv)
{ {
throw new DisplayException("SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one"); throw new DisplayException(
"SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one");
} }
else else
{ {
var name = dnsRecord.Name == "" ? rname : dnsRecord.Name + dname; var name = dnsRecord.Name == "" ? rname : dnsRecord.Name + dname;
GetData(await Client.Zones.DnsRecords.UpdateAsync(d.SharedDomain.CloudflareId, dnsRecord.Id, new ModifiedDnsRecord() GetData(await Client.Zones.DnsRecords.UpdateAsync(d.SharedDomain.CloudflareId, dnsRecord.Id,
{ new ModifiedDnsRecord()
Content = dnsRecord.Content, {
Proxied = dnsRecord.Proxied, Content = dnsRecord.Content,
Ttl = dnsRecord.Ttl, Proxied = dnsRecord.Proxied,
Name = name, Ttl = dnsRecord.Ttl,
Type = dnsRecord.Type Name = name,
})); Type = dnsRecord.Type
}));
} }
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
} }
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord) public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
@@ -198,6 +209,8 @@ public class DomainService
GetData( GetData(
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, new[] { d.Id.ToString(), dnsRecord.Name });
} }
private Domain EnsureData(Domain domain) private Domain EnsureData(Domain domain)
@@ -210,12 +223,13 @@ public class DomainService
.Include(x => x.SharedDomain) .Include(x => x.SharedDomain)
.First(x => x.Id == domain.Id); .First(x => x.Id == domain.Id);
} }
private T GetData<T>(CloudFlareResult<T> result) private T GetData<T>(CloudFlareResult<T> result)
{ {
if (!result.Success) if (!result.Success)
{ {
string message; string message;
try try
{ {
message = result.Errors.First().ErrorChain.First().Message; message = result.Errors.First().ErrorChain.First().Message;

View File

@@ -9,24 +9,26 @@ namespace Moonlight.App.Services.LogServices;
public class AuditLogService public class AuditLogService
{ {
private readonly AuditLogEntryRepository Repository; private readonly AuditLogEntryRepository Repository;
private readonly IdentityService IdentityService; private readonly IHttpContextAccessor HttpContextAccessor;
public AuditLogService(AuditLogEntryRepository repository, IdentityService identityService) public AuditLogService(
AuditLogEntryRepository repository,
IHttpContextAccessor httpContextAccessor)
{ {
Repository = repository; Repository = repository;
IdentityService = identityService; HttpContextAccessor = httpContextAccessor;
} }
public Task Log(AuditLogType type, object? data = null) public Task Log(AuditLogType type, params object[] data)
{ {
var ip = IdentityService.GetIp(); var ip = GetIp();
var entry = new AuditLogEntry() var entry = new AuditLogEntry()
{ {
Ip = ip, Ip = ip,
Type = type, Type = type,
System = false, System = false,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data) JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
}; };
Repository.Add(entry); Repository.Add(entry);
@@ -34,17 +36,30 @@ public class AuditLogService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task LogSystem(AuditLogType type, object? data = null) public Task LogSystem(AuditLogType type, params object[] data)
{ {
var entry = new AuditLogEntry() var entry = new AuditLogEntry()
{ {
Type = type, Type = type,
System = true, System = true,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data) JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
}; };
Repository.Add(entry); Repository.Add(entry);
return Task.CompletedTask; 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();
}
} }

View File

@@ -10,17 +10,17 @@ namespace Moonlight.App.Services.LogServices;
public class ErrorLogService public class ErrorLogService
{ {
private readonly ErrorLogEntryRepository Repository; private readonly ErrorLogEntryRepository Repository;
private readonly IdentityService IdentityService; private readonly IHttpContextAccessor HttpContextAccessor;
public ErrorLogService(ErrorLogEntryRepository repository, IdentityService identityService) public ErrorLogService(ErrorLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{ {
Repository = repository; Repository = repository;
IdentityService = identityService; HttpContextAccessor = httpContextAccessor;
} }
public Task Log(Exception exception, params object[] objects) public Task Log(Exception exception, params object[] objects)
{ {
var ip = IdentityService.GetIp(); var ip = GetIp();
var entry = new ErrorLogEntry() var entry = new ErrorLogEntry()
{ {
@@ -74,4 +74,17 @@ public class ErrorLogService
return fullName; 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();
}
} }

View File

@@ -9,24 +9,24 @@ namespace Moonlight.App.Services.LogServices;
public class SecurityLogService public class SecurityLogService
{ {
private readonly SecurityLogEntryRepository Repository; private readonly SecurityLogEntryRepository Repository;
private readonly IdentityService IdentityService; private readonly IHttpContextAccessor HttpContextAccessor;
public SecurityLogService(SecurityLogEntryRepository repository, IdentityService identityService) public SecurityLogService(SecurityLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{ {
Repository = repository; Repository = repository;
IdentityService = identityService; HttpContextAccessor = httpContextAccessor;
} }
public Task Log(SecurityLogType type, object? data = null) public Task Log(SecurityLogType type, params object[] data)
{ {
var ip = IdentityService.GetIp(); var ip = GetIp();
var entry = new SecurityLogEntry() var entry = new SecurityLogEntry()
{ {
Ip = ip, Ip = ip,
Type = type, Type = type,
System = false, System = false,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data) JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
}; };
Repository.Add(entry); Repository.Add(entry);
@@ -34,17 +34,30 @@ public class SecurityLogService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task LogSystem(SecurityLogType type, object? data = null) public Task LogSystem(SecurityLogType type, params object[] data)
{ {
var entry = new SecurityLogEntry() var entry = new SecurityLogEntry()
{ {
Type = type, Type = type,
System = true, System = true,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data) JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
}; };
Repository.Add(entry); Repository.Add(entry);
return Task.CompletedTask; 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();
}
} }

View File

@@ -6,6 +6,6 @@ public class MessageService : MessageSender
{ {
public MessageService() public MessageService()
{ {
Debug = true; Debug = false;
} }
} }

View File

@@ -1,9 +1,12 @@
using System.Text; using System.Text;
using JWT.Algorithms; using JWT.Algorithms;
using JWT.Builder; using JWT.Builder;
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;
@@ -11,11 +14,15 @@ 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, RevokeRepository revokeRepository) public OneTimeJwtService(ConfigService configService,
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)
@@ -51,7 +58,7 @@ public class OneTimeJwtService
return builder.Encode(); return builder.Encode();
} }
public Dictionary<string, string>? Validate(string token) public async Task<Dictionary<string, string>?> Validate(string token)
{ {
string secret = ConfigService string secret = ConfigService
.GetSection("Moonlight") .GetSection("Moonlight")
@@ -59,15 +66,18 @@ public class OneTimeJwtService
.GetValue<string>("Token"); .GetValue<string>("Token");
string json; string json;
try try
{ {
json = JwtBuilder.Create() json = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm()) .WithAlgorithm(new HMACSHA256Algorithm())
.WithSecret(secret) .WithSecret(secret)
.Decode(token); .Decode(token);
}
//TODO: Error handling, report signature errors catch (SignatureVerificationException)
{
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, token);
return null;
} }
catch (Exception e) catch (Exception e)
{ {
@@ -97,9 +107,9 @@ public class OneTimeJwtService
return opt; return opt;
} }
public void Revoke(string token) public async Task Revoke(string token)
{ {
var values = Validate(token); var values = await Validate(token);
RevokeRepository.Add(new() RevokeRepository.Add(new()
{ {

View File

@@ -6,11 +6,13 @@ using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Models.Files; using Moonlight.App.Models.Files;
using Moonlight.App.Models.Files.Accesses; using Moonlight.App.Models.Files.Accesses;
using Moonlight.App.Models.Misc;
using Moonlight.App.Models.Wings; using Moonlight.App.Models.Wings;
using Moonlight.App.Models.Wings.Requests; using Moonlight.App.Models.Wings.Requests;
using Moonlight.App.Models.Wings.Resources; using Moonlight.App.Models.Wings.Resources;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers; using Moonlight.App.Repositories.Servers;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -25,6 +27,9 @@ 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 string AppUrl; private readonly string AppUrl;
public ServerService( public ServerService(
@@ -36,7 +41,10 @@ public class ServerService
MessageService messageService, MessageService messageService,
UserService userService, UserService userService,
ConfigService configService, ConfigService configService,
WingsJwtHelper wingsJwtHelper) WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ErrorLogService errorLogService)
{ {
ServerRepository = serverRepository; ServerRepository = serverRepository;
WingsApiHelper = wingsApiHelper; WingsApiHelper = wingsApiHelper;
@@ -47,6 +55,9 @@ public class ServerService
UserService = userService; UserService = userService;
ConfigService = configService; ConfigService = configService;
WingsJwtHelper = wingsJwtHelper; WingsJwtHelper = wingsJwtHelper;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
ErrorLogService = errorLogService;
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl"); AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
} }
@@ -84,6 +95,8 @@ public class ServerService
{ {
Action = rawSignal Action = rawSignal
}); });
await AuditLogService.Log(AuditLogType.ChangePowerState, new[] { server.Uuid.ToString(), rawSignal });
} }
public async Task<ServerBackup> CreateBackup(Server server) public async Task<ServerBackup> CreateBackup(Server server)
@@ -112,6 +125,9 @@ public class ServerService
Ignore = "" Ignore = ""
}); });
await AuditLogService.Log(AuditLogType.CreateBackup,
new[] { serverData.Uuid.ToString(), backup.Uuid.ToString() });
return backup; return backup;
} }
@@ -146,6 +162,9 @@ public class ServerService
{ {
Adapter = "wings" Adapter = "wings"
}); });
await AuditLogService.Log(AuditLogType.RestoreBackup,
new[] { s.Uuid.ToString(), serverBackup.Uuid.ToString() });
} }
public async Task DeleteBackup(Server server, ServerBackup serverBackup) public async Task DeleteBackup(Server server, ServerBackup serverBackup)
@@ -165,9 +184,12 @@ public class ServerService
ServerRepository.Update(serverData); ServerRepository.Update(serverData);
await MessageService.Emit("wings.backups.delete", backup); await MessageService.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup,
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
} }
public Task<string> DownloadBackup(Server s, ServerBackup serverBackup) public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
{ {
Server server = EnsureNodeData(s); Server server = EnsureNodeData(s);
@@ -176,10 +198,11 @@ public class ServerService
claims.Add("server_uuid", server.Uuid.ToString()); claims.Add("server_uuid", server.Uuid.ToString());
claims.Add("backup_uuid", serverBackup.Uuid.ToString()); claims.Add("backup_uuid", serverBackup.Uuid.ToString());
}); });
await AuditLogService.Log(AuditLogType.DownloadBackup,
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
return Task.FromResult( return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
$"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}"
);
} }
public Task<IFileAccess> CreateFileAccess(Server s, User user) // We need the user to create the launch url public Task<IFileAccess> CreateFileAccess(Server s, User user) // We need the user to create the launch url
@@ -197,7 +220,8 @@ public class ServerService
); );
} }
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null, Action<Server>? modifyDetails = null) public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
Action<Server>? modifyDetails = null)
{ {
var user = UserRepository var user = UserRepository
.Get() .Get()
@@ -267,8 +291,8 @@ public class ServerService
Value = imageVariable.DefaultValue Value = imageVariable.DefaultValue
}); });
} }
if(modifyDetails != null) if (modifyDetails != null)
modifyDetails.Invoke(server); modifyDetails.Invoke(server);
var newServerData = ServerRepository.Add(server); var newServerData = ServerRepository.Add(server);
@@ -281,16 +305,17 @@ public class ServerService
StartOnCompletion = false StartOnCompletion = false
}); });
await AuditLogService.Log(AuditLogType.CreateServer, newServerData.Uuid.ToString());
return newServerData; return newServerData;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error("Error creating server on wings. Deleting db model"); await ErrorLogService.Log(e, new[] { newServerData.Uuid.ToString(), node.Id.ToString() });
Logger.Error(e);
ServerRepository.Delete(newServerData); ServerRepository.Delete(newServerData);
throw new Exception("Error creating server on wings"); throw new DisplayException("Error creating server on wings");
} }
} }
@@ -299,14 +324,19 @@ public class ServerService
Server server = EnsureNodeData(s); Server server = EnsureNodeData(s);
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null); await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
await AuditLogService.Log(AuditLogType.ReinstallServer, server.Uuid.ToString());
} }
public async Task<Server> SftpServerLogin(int serverId, int id, string password) public async Task<Server> SftpServerLogin(int serverId, int id, string password)
{ {
var server = ServerRepository.Get().FirstOrDefault(x => x.Id == serverId); var server = ServerRepository.Get().FirstOrDefault(x => x.Id == serverId);
if (server == null) //TODO: Logging if (server == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, serverId);
throw new Exception("Server not found"); throw new Exception("Server not found");
}
var user = await UserService.SftpLogin(id, password); var user = await UserService.SftpLogin(id, password);
@@ -316,6 +346,7 @@ public class ServerService
} }
else else
{ {
//TODO: Decide if logging
throw new Exception("User and owner id do not match"); throw new Exception("User and owner id do not match");
} }
} }

View File

@@ -4,7 +4,9 @@ using JWT.Builder;
using JWT.Exceptions; using JWT.Exceptions;
using Logging.Net; using Logging.Net;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
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;
@@ -13,6 +15,8 @@ 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;
@@ -22,11 +26,15 @@ 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")
@@ -81,14 +89,12 @@ public class IdentityService
} }
catch (SignatureVerificationException) catch (SignatureVerificationException)
{ {
//TODO: Heavy warn and write it to the logs await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, token);
Logger.Warn("Someone tried to modify his data: " + token);
return null; return null;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error parsing and validating token"); await ErrorLogService.Log(e);
Logger.Warn(e);
return null; return null;
} }
@@ -117,19 +123,14 @@ public class IdentityService
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime; var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime;
if (issuedAt < user.TokenValidTime.ToUniversalTime()) if (issuedAt < user.TokenValidTime.ToUniversalTime())
{
//TODO: Remove at publish
//Logger.Debug($"Old token found: {issuedAt.ToShortDateString()} {issuedAt.ToShortTimeString()} Current valid token time {userData.TokenValidTime.ToUniversalTime().ToShortDateString()} {userData.TokenValidTime.ToUniversalTime().ToShortTimeString()}");
return null; return null;
}
UserCache = user; UserCache = user;
return UserCache; return UserCache;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error loading user"); await ErrorLogService.Log(e);
Logger.Warn(e);
return null; return null;
} }
} }

View File

@@ -1,8 +1,10 @@
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.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Subscriptions; using Moonlight.App.Repositories.Subscriptions;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -14,18 +16,21 @@ public class SubscriptionService
private readonly IdentityService IdentityService; private readonly IdentityService IdentityService;
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly OneTimeJwtService OneTimeJwtService; private readonly OneTimeJwtService OneTimeJwtService;
private readonly AuditLogService AuditLogService;
public SubscriptionService(SubscriptionRepository subscriptionRepository, public SubscriptionService(SubscriptionRepository subscriptionRepository,
UserRepository userRepository, UserRepository userRepository,
IdentityService identityService, IdentityService identityService,
ConfigService configService, ConfigService configService,
OneTimeJwtService oneTimeJwtService) OneTimeJwtService oneTimeJwtService,
AuditLogService auditLogService)
{ {
SubscriptionRepository = subscriptionRepository; SubscriptionRepository = subscriptionRepository;
UserRepository = userRepository; UserRepository = userRepository;
IdentityService = identityService; IdentityService = identityService;
ConfigService = configService; ConfigService = configService;
OneTimeJwtService = oneTimeJwtService; OneTimeJwtService = oneTimeJwtService;
AuditLogService = auditLogService;
} }
public async Task<Subscription?> Get() public async Task<Subscription?> Get()
@@ -50,6 +55,8 @@ public class SubscriptionService
var user = await IdentityService.Get(); var user = await IdentityService.Get();
user!.Subscription = null; user!.Subscription = null;
UserRepository.Update(user!); UserRepository.Update(user!);
await AuditLogService.Log(AuditLogType.CancelSubscription, new[] { user.Email });
} }
public Task<Subscription[]> GetAvailable() public Task<Subscription[]> GetAvailable()
{ {
@@ -91,7 +98,7 @@ public class SubscriptionService
public async Task ApplyCode(string code) public async Task ApplyCode(string code)
{ {
var user = (await IdentityService.Get())!; var user = (await IdentityService.Get())!;
var values = OneTimeJwtService.Validate(code); var values = await OneTimeJwtService.Validate(code);
if (values == null) if (values == null)
throw new DisplayException("Invalid subscription code"); throw new DisplayException("Invalid subscription code");
@@ -114,6 +121,8 @@ public class SubscriptionService
UserRepository.Update(user); UserRepository.Update(user);
OneTimeJwtService.Revoke(code); await OneTimeJwtService.Revoke(code);
await AuditLogService.Log(AuditLogType.ApplySubscriptionCode, new[] { user.Email, subscription.Id.ToString() });
} }
} }

View File

@@ -1,4 +1,6 @@
using Moonlight.App.Repositories; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
using OtpNet; using OtpNet;
@@ -8,11 +10,16 @@ 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(IdentityService identityService, UserRepository userRepository) public TotpService(
IdentityService identityService,
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)
@@ -38,21 +45,25 @@ public class TotpService
public async Task Enable() public async Task Enable()
{ {
var user = await IdentityService.Get(); var user = (await IdentityService.Get())!;
user.TotpEnabled = true; user.TotpEnabled = true;
user.TotpSecret = GenerateSecret(); user.TotpSecret = GenerateSecret();
UserRepository.Update(user); UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.EnableTotp, user.Email);
} }
public async Task Disable() public async Task Disable()
{ {
var user = await IdentityService.Get(); var user = (await IdentityService.Get())!;
user.TotpEnabled = false; user.TotpEnabled = false;
UserRepository.Update(user); UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.DisableTotp, user.Email);
} }
private string GenerateSecret() private string GenerateSecret()

View File

@@ -1,10 +1,10 @@
using JWT.Algorithms; using JWT.Algorithms;
using JWT.Builder; using JWT.Builder;
using Logging.Net;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Models.Misc; 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;
@@ -12,20 +12,24 @@ public class UserService
{ {
private readonly UserRepository UserRepository; private readonly UserRepository UserRepository;
private readonly TotpService TotpService; private readonly TotpService TotpService;
private readonly ConfigService ConfigService; private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly string JwtSecret; private readonly string JwtSecret;
public UserService( public UserService(
UserRepository userRepository, UserRepository userRepository,
TotpService totpService, TotpService totpService,
ConfigService configService) ConfigService configService,
SecurityLogService securityLogService,
AuditLogService auditLogService)
{ {
UserRepository = userRepository; UserRepository = userRepository;
TotpService = totpService; TotpService = totpService;
ConfigService = configService; SecurityLogService = securityLogService;
AuditLogService = auditLogService;
JwtSecret = ConfigService JwtSecret = configService
.GetSection("Moonlight") .GetSection("Moonlight")
.GetSection("Security") .GetSection("Security")
.GetValue<string>("Token"); .GetValue<string>("Token");
@@ -67,11 +71,13 @@ public class UserService
//var mail = new WelcomeMail(user); //var mail = new WelcomeMail(user);
//await MailService.Send(mail, user); //await MailService.Send(mail, user);
await AuditLogService.Log(AuditLogType.Register, user.Email);
return await GenerateToken(user); return await GenerateToken(user);
} }
public Task<bool> CheckTotp(string email, string password) public async Task<bool> CheckTotp(string email, string password)
{ {
var user = UserRepository.Get() var user = UserRepository.Get()
.FirstOrDefault( .FirstOrDefault(
@@ -80,18 +86,16 @@ public class UserService
if (user == null) if (user == null)
{ {
Logger.Debug("User not found"); await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
//AuditLogService.Log("login:fail", $"Invalid email: {email}. Password: {password}");
throw new DisplayException("Email and password combination not found"); throw new DisplayException("Email and password combination not found");
} }
if (BCrypt.Net.BCrypt.Verify(password, user.Password)) if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{ {
return Task.FromResult(user.TotpEnabled); return user.TotpEnabled;
} }
//AuditLogService.Log("login:fail", $"Invalid email: {email}. Password: {password}"); await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
throw new DisplayException("Email and password combination not found");; throw new DisplayException("Email and password combination not found");;
} }
@@ -111,29 +115,27 @@ public class UserService
if (string.IsNullOrEmpty(totpCode)) if (string.IsNullOrEmpty(totpCode))
throw new DisplayException("2FA code must be provided"); throw new DisplayException("2FA code must be provided");
var totpCodeValid = await TotpService.Verify(user.TotpSecret, totpCode); var totpCodeValid = await TotpService.Verify(user!.TotpSecret, totpCode);
if (totpCodeValid) if (totpCodeValid)
{ {
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in"); await AuditLogService.Log(AuditLogType.Login, email);
return await GenerateToken(user); return await GenerateToken(user);
} }
else else
{ {
//AuditLogService.Log("login:fail", $"Invalid totp code: {totpCode}"); await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
throw new DisplayException("2FA code invalid"); throw new DisplayException("2FA code invalid");
} }
} }
else else
{ {
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in"); await AuditLogService.Log(AuditLogType.Login, email);
return await GenerateToken(user!); return await GenerateToken(user!);
} }
} }
public Task ChangePassword(User user, string password) public async Task ChangePassword(User user, string password)
{ {
user.Password = BCrypt.Net.BCrypt.HashPassword(password); user.Password = BCrypt.Net.BCrypt.HashPassword(password);
user.TokenValidTime = DateTime.Now; user.TokenValidTime = DateTime.Now;
@@ -141,26 +143,27 @@ public class UserService
//var mail = new NewPasswordMail(user); //var mail = new NewPasswordMail(user);
//await MailService.Send(mail, user); //await MailService.Send(mail, user);
//AuditLogService.Log("password:change", "The password has been set to a new one"); await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
return Task.CompletedTask;
} }
public Task<User> SftpLogin(int id, string password) public async Task<User> SftpLogin(int id, string password)
{ {
var user = UserRepository.Get().FirstOrDefault(x => x.Id == id); var user = UserRepository.Get().FirstOrDefault(x => x.Id == id);
if (user == null) if (user == null)
throw new Exception("Unknown user"); {
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, id);
throw new Exception("Invalid username");
}
if (BCrypt.Net.BCrypt.Verify(password, user.Password)) if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{ {
//TODO: Maybe log await AuditLogService.LogSystem(AuditLogType.Login, user.Email);
return Task.FromResult(user); return user;
} }
//TODO: Log await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, new[] { id.ToString(), password });
throw new Exception("Invalid userid or password"); throw new Exception("Invalid userid or password");
} }

View File

@@ -10,6 +10,7 @@ using Moonlight.App.Repositories.Servers;
using Moonlight.App.Repositories.Subscriptions; using Moonlight.App.Repositories.Subscriptions;
using Moonlight.App.Services; using Moonlight.App.Services;
using Moonlight.App.Services.Interop; using Moonlight.App.Services.Interop;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Notifications; using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.OAuth2; using Moonlight.App.Services.OAuth2;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
@@ -80,7 +81,10 @@ namespace Moonlight
builder.Services.AddScoped<GoogleOAuth2Service>(); builder.Services.AddScoped<GoogleOAuth2Service>();
builder.Services.AddScoped<DiscordOAuth2Service>(); builder.Services.AddScoped<DiscordOAuth2Service>();
// Loggers
builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<ErrorLogService>();
// Support // Support
builder.Services.AddSingleton<SupportServerService>(); builder.Services.AddSingleton<SupportServerService>();

View File

@@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>Password change for</TL> @(Data[0])
}
else
{
<TL>Password change for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories.Servers
@inject ServerRepository ServerRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (Server == null)
{
<TL>Change power state for</TL> @(Data[0]) <TL>to</TL> @(Data[1])
}
else
{
<TL>Change power state for</TL> <a href="/admin/servers/edit/@(Server.Id)">@(Server.Name)</a> <TL>to</TL> @(Data[1])
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private Server? Server;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0]));
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>New login for</TL> @(Data[0])
}
else
{
<TL>New login for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>Register for</TL> @(Data[0])
}
else
{
<TL>Register for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -1,9 +1,10 @@
@using Logging.Net @using Logging.Net
@using Moonlight.App.Services.LogServices
@using Moonlight.App.Services.Sessions @using Moonlight.App.Services.Sessions
@inherits ErrorBoundary @inherits ErrorBoundary
@inject IdentityService IdentityService @inject ErrorLogService ErrorLogService
@if (CurrentException is null) @if (CurrentException is null)
{ {
@@ -52,13 +53,7 @@ else
{ {
receivedExceptions.Add(exception); receivedExceptions.Add(exception);
var user = await IdentityService.Get(); await ErrorLogService.Log(exception);
var id = user == null ? -1 : user.Id;
Logger.Error($"[{id}] An unhanded exception occured:");
Logger.Error(exception);
//TODO: Create error report
await base.OnErrorAsync(exception); await base.OnErrorAsync(exception);
} }

View File

@@ -13,6 +13,8 @@
{ {
protected override async Task OnErrorAsync(Exception exception) protected override async Task OnErrorAsync(Exception exception)
{ {
Logger.Debug(exception);
if (exception is DisplayException displayException) if (exception is DisplayException displayException)
{ {
await AlertService.Error( await AlertService.Error(

View File

@@ -0,0 +1,25 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/system">
Overview
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/system/auditlog">
AuditLog
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -93,7 +93,7 @@ else
</a> </a>
</div> </div>
<div class="menu-item"> <div class="menu-item">
<a class="menu-link" href="/admin/general"> <a class="menu-link" href="/admin/system">
<span class="menu-icon"> <span class="menu-icon">
<i class="bx bx-chip"></i> <i class="bx bx-chip"></i>
</span> </span>

View File

@@ -44,11 +44,11 @@
} }
} }
</select> </select>
<WorkerButton <WButton
OnClick="Save" OnClick="Save"
Text="@(TranslationService.Translate("Change"))" Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))" WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WorkerButton> CssClasses="btn-primary"></WButton>
</LazyLoader> </LazyLoader>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,106 @@
@page "/admin/system/auditlog"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Repositories.LogEntries
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities.LogsEntries
@using BlazorTable
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.Shared.Components.AuditLogEntrys
@inject AuditLogEntryRepository AuditLogEntryRepository
<OnlyAdmin>
<AdminSystemNavigation Index="1"/>
<div class="card">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<span class="me-3 lh-0">
<i class="bx bx-md bx-calendar"></i>
</span>
<h3 class="fw-bold m-0 text-gray-800">@(Formatter.FormatDateOnly(DateTime))</h3>
</div>
<div class="card-toolbar m-0">
<ul class="nav nav-tabs nav-line-tabs nav-stretch fs-6 border-0 fw-bold">
<li class="nav-item">
<button @onclick="Left" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-left-arrow"></i>
</button>
</li>
<li class="nav-item">
<button @onclick="Right" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-right-arrow"></i>
</button>
</li>
</ul>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (AuditLogEntries.Any())
{
<div class="timeline">
@foreach (var entry in AuditLogEntries)
{
switch (entry.Type)
{
case AuditLogType.Login:
<AuditLogEntryLogin Entry="entry"/>
break;
case AuditLogType.Register:
<AuditLogEntryRegister Entry="entry"/>
break;
case AuditLogType.ChangePassword:
<AuditLogEntryChangePassword Entry="entry"/>
break;
case AuditLogType.ChangePowerState:
<AuditLogEntryChangePowerState Entry="entry"/>
break;
}
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No records found for this day</TL>
</div>
}
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private AuditLogEntry[] AuditLogEntries;
private LazyLoader LazyLoader;
private DateTime DateTime = DateTime.Today;
private Task Load(LazyLoader arg)
{
AuditLogEntries = AuditLogEntryRepository
.Get()
.Where(x => x.CreatedAt.Date == DateTime.Date)
.OrderByDescending(x => x.Id)
.ToArray();
return Task.CompletedTask;
}
private async Task Left()
{
DateTime = DateTime.AddDays(1);
await LazyLoader.Reload();
}
private async Task Right()
{
DateTime = DateTime.AddDays(-1);
await LazyLoader.Reload();
}
}

View File

@@ -0,0 +1,9 @@
@page "/admin/system"
@using Moonlight.Shared.Components.Navigations
<OnlyAdmin>
<AdminSystemNavigation Index="0" />
</OnlyAdmin>

View File

@@ -31,6 +31,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQWxlcnRzXFNldHVwQ29tcGxldGVkQWxlcnQucmF6b3I= build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQWxlcnRzXFNldHVwQ29tcGxldGVkQWxlcnQucmF6b3I=
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryLogin.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQXVkaXRMb2dFbnRyeXNcQXVkaXRMb2dFbnRyeUxvZ2luLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Auth/Login.razor] [C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Auth/Login.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQXV0aFxMb2dpbi5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQXV0aFxMb2dpbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@@ -67,6 +71,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcTmF2aWdhdGlvbnNcQWRtaW5TeXN0ZW1OYXZpZ2F0aW9uLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor] [C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcTmF2aWdhdGlvbnNcUHJvZmlsZU5hdmlnYXRpb24ucmF6b3I= build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcTmF2aWdhdGlvbnNcUHJvZmlsZU5hdmlnYXRpb24ucmF6b3I=
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
@@ -227,6 +235,18 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN1cHBvcnRcVmlldy5yYXpvcg== build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN1cHBvcnRcVmlldy5yYXpvcg==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/AuditLog.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xBdWRpdExvZy5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/Index.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xJbmRleC5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/Logging.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xMb2dnaW5nLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Domains.razor] [C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Domains.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXERvbWFpbnMucmF6b3I= build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXERvbWFpbnMucmF6b3I=
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =

View File

@@ -257,3 +257,13 @@ Cancel Subscription;Cancel Subscription
Active until;Active until Active until;Active until
We will send you a notification upon subscription expiration;We will send you a notification upon subscription expiration We will send you a notification upon subscription expiration;We will send you a notification upon subscription expiration
This token has been already used;This token has been already used This token has been already used;This token has been already used
New login for;New login for
No records found for this day;No records found for this day
Change;Change
Changing;Changing
Minecraft version;Minecraft version
Build version;Build version
Server installation is currently running;Server installation is currently running
Selected;Selected
Move deleted;Move deleted
Delete selected;Delete selected