diff --git a/.gitignore b/.gitignore index 0290c0055..75589ba35 100644 --- a/.gitignore +++ b/.gitignore @@ -443,5 +443,6 @@ $RECYCLE.BIN/ # App specific appsettings.json -/API/datingapp.db-shm -/API/datingapp.db-wal \ No newline at end of file +/API/kavita.db +/API/kavita.db-shm +/API/kavita.db-wal \ No newline at end of file diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 19903e2f0..d483617a7 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -38,7 +38,8 @@ namespace API.Controllers { UserName = registerDto.Username.ToLower(), PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)), - PasswordSalt = hmac.Key + PasswordSalt = hmac.Key, + IsAdmin = registerDto.IsAdmin }; _context.Users.Add(user); @@ -47,7 +48,8 @@ namespace API.Controllers return new UserDto() { Username = user.UserName, - Token = _tokenService.CreateToken(user) + Token = _tokenService.CreateToken(user), + IsAdmin = user.IsAdmin }; } diff --git a/API/Converters/JsonBoolNumberConverter.cs b/API/Converters/JsonBoolNumberConverter.cs new file mode 100644 index 000000000..c370fb9fd --- /dev/null +++ b/API/Converters/JsonBoolNumberConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace API.Converters +{ + /// + /// Converts a number to a boolean. + /// This is needed for HDHomerun. + /// + public class JsonBoolNumberConverter : JsonConverter + { + /// + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return Convert.ToBoolean(reader.GetInt32()); + } + + return reader.GetBoolean(); + } + + /// + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } +} \ No newline at end of file diff --git a/API/Converters/JsonBoolStringConverter.cs b/API/Converters/JsonBoolStringConverter.cs new file mode 100644 index 000000000..d17a6537a --- /dev/null +++ b/API/Converters/JsonBoolStringConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace API.Converters +{ + public class JsonBoolStringConverter : JsonConverter + { + /// + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + return reader.GetString().ToLower() == "true"; + } + + return reader.GetBoolean(); + } + + /// + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } +} \ No newline at end of file diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs index 9110f298d..34e1a7a60 100644 --- a/API/DTOs/RegisterDto.cs +++ b/API/DTOs/RegisterDto.cs @@ -1,4 +1,6 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using API.Converters; namespace API.DTOs { @@ -9,5 +11,7 @@ namespace API.DTOs [Required] [StringLength(8, MinimumLength = 4)] public string Password { get; set; } + [JsonConverter(typeof(JsonBoolNumberConverter))] + public bool IsAdmin { get; set; } } } \ No newline at end of file diff --git a/API/DTOs/UserDto.cs b/API/DTOs/UserDto.cs index 15de1b8d9..c8b97abb6 100644 --- a/API/DTOs/UserDto.cs +++ b/API/DTOs/UserDto.cs @@ -4,5 +4,6 @@ { public string Username { get; set; } public string Token { get; set; } + public bool IsAdmin { get; set; } } } \ No newline at end of file diff --git a/API/Data/Migrations/20201213205325_AddUser.Designer.cs b/API/Data/Migrations/20201213205325_AddUser.Designer.cs new file mode 100644 index 000000000..565d03517 --- /dev/null +++ b/API/Data/Migrations/20201213205325_AddUser.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20201213205325_AddUser")] + partial class AddUser + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("BLOB"); + + b.Property("PasswordSalt") + .HasColumnType("BLOB"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20201213205325_AddUser.cs b/API/Data/Migrations/20201213205325_AddUser.cs new file mode 100644 index 000000000..4429111b1 --- /dev/null +++ b/API/Data/Migrations/20201213205325_AddUser.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class AddUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserName = table.Column(type: "TEXT", nullable: true), + PasswordHash = table.Column(type: "BLOB", nullable: true), + PasswordSalt = table.Column(type: "BLOB", nullable: true), + Created = table.Column(type: "TEXT", nullable: false), + LastActive = table.Column(type: "TEXT", nullable: false), + IsAdmin = table.Column(type: "INTEGER", nullable: false), + RowVersion = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 000000000..9ed038826 --- /dev/null +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("BLOB"); + + b.Property("PasswordSalt") + .HasColumnType("BLOB"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Entities/AppUser.cs b/API/Entities/AppUser.cs index 3478b74f0..a15e894c4 100644 --- a/API/Entities/AppUser.cs +++ b/API/Entities/AppUser.cs @@ -13,6 +13,7 @@ namespace API.Entities public byte[] PasswordSalt { get; set; } public DateTime Created { get; set; } = DateTime.Now; public DateTime LastActive { get; set; } + public bool IsAdmin { get; set; } [ConcurrencyCheck] public uint RowVersion { get; set; } @@ -21,8 +22,6 @@ namespace API.Entities { RowVersion++; } - - - + } } \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index 0f618b9ff..6901678f3 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -2,8 +2,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using API.Data; using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -11,9 +14,26 @@ namespace API { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + var host = CreateHostBuilder(args).Build(); + + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; + + try + { + var context = services.GetRequiredService(); + // Apply all migrations on startup + await context.Database.MigrateAsync(); + } + catch (Exception ex) + { + var logger = services.GetRequiredService < ILogger>(); + logger.LogError(ex, "An error occurred during migration"); + } + + await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) => diff --git a/API/Properties/launchSettings.json b/API/Properties/launchSettings.json index 85ac7db6f..92f840e5c 100644 --- a/API/Properties/launchSettings.json +++ b/API/Properties/launchSettings.json @@ -11,7 +11,7 @@ "profiles": { "IIS Express": { "commandName": "IISExpress", - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development"