From bb8e972a755df4e42ace224983df210cbbdf8c8f Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 14 Aug 2023 13:49:27 +0200 Subject: [PATCH 1/3] feat: efcore & postgres setup --- Server/DB/Documents/Account.cs | 6 +++-- Server/DB/WonderkingContext.cs | 33 +++++++++++++++++++++++---- Server/PacketHandlers/LoginHandler.cs | 12 +++++----- Server/Program.cs | 17 +++++++------- Server/Server.csproj | 11 +++++++-- 5 files changed, 56 insertions(+), 23 deletions(-) diff --git a/Server/DB/Documents/Account.cs b/Server/DB/Documents/Account.cs index 8914684..746225f 100644 --- a/Server/DB/Documents/Account.cs +++ b/Server/DB/Documents/Account.cs @@ -1,9 +1,11 @@ namespace Server.DB.Documents; -using CouchDB.Driver.Types; +using Microsoft.EntityFrameworkCore; -public class Account : CouchDocument +public class Account { + public Guid Id { get; set; } + public Account(string username, byte[] password, string email, byte permissionLevel, byte[] salt) { this.Username = username; diff --git a/Server/DB/WonderkingContext.cs b/Server/DB/WonderkingContext.cs index 687464b..8a58af5 100644 --- a/Server/DB/WonderkingContext.cs +++ b/Server/DB/WonderkingContext.cs @@ -1,14 +1,37 @@ namespace Server.DB; -using CouchDB.Driver; -using CouchDB.Driver.Options; +using System.Data.Common; using Documents; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Npgsql; -public class WonderkingContext : CouchContext +public class WonderkingContext : DbContext { - public WonderkingContext(CouchOptions options) : base(options) + private readonly ILoggerFactory loggerFactory; + private readonly IConfiguration configuration; + + public WonderkingContext(ILoggerFactory loggerFactory, IConfiguration configuration) { + this.loggerFactory = loggerFactory; + this.configuration = configuration; } - public CouchDatabase Accounts { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => + optionsBuilder + .UseNpgsql( + $"Host={this.configuration["DB:Host"]};Username={this.configuration["DB:Username"]};Password={this.configuration["DB:Password"]};Database={this.configuration["DB:Database"]};Port={this.configuration["DB:Port"]}") + .EnableSensitiveDataLogging().UseLoggerFactory(this.loggerFactory); + + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.Entity(builder => + { + builder.Property(b => b.Username).HasColumnType("varchar(20)"); + builder.Property(b => b.Password).HasColumnType("bytea"); + builder.Property(b => b.Salt).HasColumnType("bytea"); + builder.HasKey(b => b.Id); + }); + + public DbSet Accounts { get; set; } } diff --git a/Server/PacketHandlers/LoginHandler.cs b/Server/PacketHandlers/LoginHandler.cs index a85f34a..05d1566 100644 --- a/Server/PacketHandlers/LoginHandler.cs +++ b/Server/PacketHandlers/LoginHandler.cs @@ -2,10 +2,8 @@ using System.Security.Cryptography; using System.Text; -using CouchDB.Driver.Query.Extensions; using DB; using DB.Documents; -using DotNext; using Konscious.Security.Cryptography; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -47,10 +45,12 @@ public class LoginHandler : IPacketHandler var finalAccount = await this.wonderkingContext.Accounts.AddAsync(new Account(packet.Username, Array.Empty(), "", 0, argon2Id.Salt)); - argon2Id.AssociatedData = Guid.Parse(finalAccount.Id).ToByteArray(); - finalAccount.Password = await argon2Id.GetBytesAsync(128); - await this.wonderkingContext.Accounts.AddOrUpdateAsync(finalAccount); + await this.wonderkingContext.SaveChangesAsync(); + argon2Id.AssociatedData = finalAccount.Entity.Id.ToByteArray(); + finalAccount.Entity.Password = await argon2Id.GetBytesAsync(128); + this.wonderkingContext.Accounts.Update(finalAccount.Entity); loginResponseReason = LoginResponseReason.Ok; + await this.wonderkingContext.SaveChangesAsync(); } else { @@ -62,7 +62,7 @@ public class LoginHandler : IPacketHandler else { argon2Id.Salt = account.Salt; - argon2Id.AssociatedData = Guid.Parse(account.Id).ToByteArray(); + argon2Id.AssociatedData = account.Id.ToByteArray(); var tempPasswordBytes = await argon2Id.GetBytesAsync(128); loginResponseReason = tempPasswordBytes.SequenceEqual(account.Password) ? LoginResponseReason.Ok diff --git a/Server/Program.cs b/Server/Program.cs index 5666de1..d2e9ce0 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,8 +1,8 @@ #pragma warning disable AV1500 using System.Net; using System.Reflection; -using CouchDB.Driver.DependencyInjection; using MassTransit; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -16,13 +16,8 @@ var configurationRoot = builder.Configuration.AddJsonFile("settings.json", false builder.Services.AddLogging(); builder.Logging.AddFile("Logs/Server-{Date}.log", LogLevel.Debug); builder.Logging.AddFile("Logs/Server-{Date}.json.log", LogLevel.Debug, isJson: true); -builder.Services.AddCouchContext(cfg => -{ - cfg.UseEndpoint(configurationRoot["DB:Endpoint"] ?? throw new InvalidOperationException()) - .UseBasicAuthentication(configurationRoot["DB:User"] ?? throw new InvalidOperationException(), - configurationRoot["DB:Password"] ?? throw new InvalidOperationException()) - .EnsureDatabaseExists(); -}); +builder.Services.AddEntityFrameworkNpgsql(); +builder.Services.AddDbContext(); builder.Services.AddSingleton(); builder.Services.AddHostedService(provider => provider.GetService() ?? throw new InvalidOperationException()); @@ -36,5 +31,11 @@ builder.Services.AddHostedService(provider => new Wonderki provider.GetService() ?? throw new InvalidOperationException())); using var host = builder.Build(); +await using (var scope = host.Services.CreateAsyncScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + await db.Database.MigrateAsync(); +} + await host.RunAsync(); #pragma warning restore AV1500 diff --git a/Server/Server.csproj b/Server/Server.csproj index 108f613..a560cca 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -17,8 +17,6 @@ - - @@ -36,6 +34,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -49,6 +55,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive -- 2.45.2 From 81a3d24eebc797108983f6bfe95c52639cf8e1ca Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 14 Aug 2023 13:49:48 +0200 Subject: [PATCH 2/3] feat: initial migration --- .../20230814114414_Initial.Designer.cs | 60 +++++++++++++++++++ .../DB/Migrations/20230814114414_Initial.cs | 38 ++++++++++++ .../WonderkingContextModelSnapshot.cs | 57 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 Server/DB/Migrations/20230814114414_Initial.Designer.cs create mode 100644 Server/DB/Migrations/20230814114414_Initial.cs create mode 100644 Server/DB/Migrations/WonderkingContextModelSnapshot.cs diff --git a/Server/DB/Migrations/20230814114414_Initial.Designer.cs b/Server/DB/Migrations/20230814114414_Initial.Designer.cs new file mode 100644 index 0000000..9cb6461 --- /dev/null +++ b/Server/DB/Migrations/20230814114414_Initial.Designer.cs @@ -0,0 +1,60 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Server.DB; + +#nullable disable + +namespace Server.DB.Migrations +{ + [DbContext(typeof(WonderkingContext))] + [Migration("20230814114414_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PermissionLevel") + .HasColumnType("smallint"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20230814114414_Initial.cs b/Server/DB/Migrations/20230814114414_Initial.cs new file mode 100644 index 0000000..0a8961f --- /dev/null +++ b/Server/DB/Migrations/20230814114414_Initial.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.DB.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Accounts", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Username = table.Column(type: "varchar(20)", nullable: false), + Password = table.Column(type: "bytea", nullable: false), + Email = table.Column(type: "text", nullable: false), + PermissionLevel = table.Column(type: "smallint", nullable: false), + Salt = table.Column(type: "bytea", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Accounts", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Accounts"); + } + } +} diff --git a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs new file mode 100644 index 0000000..5b2e1f5 --- /dev/null +++ b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs @@ -0,0 +1,57 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Server.DB; + +#nullable disable + +namespace Server.DB.Migrations +{ + [DbContext(typeof(WonderkingContext))] + partial class WonderkingContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("PermissionLevel") + .HasColumnType("smallint"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} -- 2.45.2 From 6e8aaca7f0d79349ed2f2339abb3373763d265ba Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 14 Aug 2023 13:50:36 +0200 Subject: [PATCH 3/3] config: add default testing config --- .gitignore | 2 -- Server/settings.json | 12 ++++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Server/settings.json diff --git a/.gitignore b/.gitignore index 3357906..c2a26ef 100644 --- a/.gitignore +++ b/.gitignore @@ -480,5 +480,3 @@ $RECYCLE.BIN/ .idea .vscode - -settings.json \ No newline at end of file diff --git a/Server/settings.json b/Server/settings.json new file mode 100644 index 0000000..8b9e207 --- /dev/null +++ b/Server/settings.json @@ -0,0 +1,12 @@ +{ + "DB": { + "Username": "continuitydevuser", + "Host": "perf.rainote.dev", + "Password": "7>CU`D2/LKw6hD/7", + "Database": "continuity", + "Port": "13543" + }, + "Testing": { + "CreateAccountOnLogin": true + } +} -- 2.45.2