Finished database management and user authentication
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
13
.idea/.idea.HopFrame/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.HopFrame/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/contentModel.xml
|
||||
/.idea.HopFrame.iml
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
8
.idea/.idea.HopFrame/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.HopFrame/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/.idea.HopFrame/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.HopFrame/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
3
DatabaseTest/.gitignore
vendored
Normal file
3
DatabaseTest/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
obj
|
||||
bin
|
||||
appsettings.Development.json
|
||||
9
DatabaseTest/Controllers/TestController.cs
Normal file
9
DatabaseTest/Controllers/TestController.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using HopFrame.Api.Controller;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DatabaseTest.Controllers;
|
||||
|
||||
[ApiController]
|
||||
public class TestController(DatabaseContext context) : SecurityController<DatabaseContext>(context) {
|
||||
|
||||
}
|
||||
12
DatabaseTest/DatabaseContext.cs
Normal file
12
DatabaseTest/DatabaseContext.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using HopFrame.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DatabaseTest;
|
||||
|
||||
public class DatabaseContext : HopDbContextBase {
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
|
||||
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\DatabaseTest\\bin\\Debug\\net7.0\\test.db;Mode=ReadWrite;");
|
||||
}
|
||||
}
|
||||
25
DatabaseTest/DatabaseTest.csproj
Normal file
25
DatabaseTest/DatabaseTest.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HopFrame.Api\HopFrame.Api.csproj" />
|
||||
<ProjectReference Include="..\HopFrame.Security\HopFrame.Security.csproj" />
|
||||
<ProjectReference Include="..\HopFrame.Database\HopFrame.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
75
DatabaseTest/Migrations/20240712130909_Initial.Designer.cs
generated
Normal file
75
DatabaseTest/Migrations/20240712130909_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DatabaseTest;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240712130909_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.20");
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.PermissionEntry", b =>
|
||||
{
|
||||
b.Property<long>("RecordId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("GrantedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PermissionText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RecordId");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.UserEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
56
DatabaseTest/Migrations/20240712130909_Initial.cs
Normal file
56
DatabaseTest/Migrations/20240712130909_Initial.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permissions",
|
||||
columns: table => new
|
||||
{
|
||||
RecordId = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
PermissionText = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
|
||||
OwnerId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
GrantedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Permissions", x => x.RecordId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Username = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
Email = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
Password = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Permissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
101
DatabaseTest/Migrations/20240712143345_Tokens.Designer.cs
generated
Normal file
101
DatabaseTest/Migrations/20240712143345_Tokens.Designer.cs
generated
Normal file
@@ -0,0 +1,101 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DatabaseTest;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240712143345_Tokens")]
|
||||
partial class Tokens
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.20");
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.PermissionEntry", b =>
|
||||
{
|
||||
b.Property<long>("RecordId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("GrantedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PermissionText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RecordId");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.UserEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Security.Models.TokenEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasMaxLength(1)
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
38
DatabaseTest/Migrations/20240712143345_Tokens.cs
Normal file
38
DatabaseTest/Migrations/20240712143345_Tokens.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Tokens : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tokens",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Type = table.Column<int>(type: "INTEGER", maxLength: 1, nullable: false),
|
||||
Token = table.Column<string>(type: "TEXT", maxLength: 36, nullable: false),
|
||||
UserId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tokens", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tokens");
|
||||
}
|
||||
}
|
||||
}
|
||||
100
DatabaseTest/Migrations/20240713083821_Security.Designer.cs
generated
Normal file
100
DatabaseTest/Migrations/20240713083821_Security.Designer.cs
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DatabaseTest;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20240713083821_Security")]
|
||||
partial class Security
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.PermissionEntry", b =>
|
||||
{
|
||||
b.Property<long>("RecordId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("GrantedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PermissionText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RecordId");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.TokenEntry", b =>
|
||||
{
|
||||
b.Property<string>("Token")
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasMaxLength(1)
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Token");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.UserEntry", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
109
DatabaseTest/Migrations/20240713083821_Security.cs
Normal file
109
DatabaseTest/Migrations/20240713083821_Security.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Security : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Tokens",
|
||||
table: "Tokens");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Id",
|
||||
table: "Tokens");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OwnerId",
|
||||
table: "Permissions");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Id",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
maxLength: 36,
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "INTEGER")
|
||||
.OldAnnotation("Sqlite:Autoincrement", true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "UserId",
|
||||
table: "Tokens",
|
||||
type: "TEXT",
|
||||
maxLength: 36,
|
||||
nullable: false,
|
||||
oldClrType: typeof(long),
|
||||
oldType: "INTEGER");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "UserId",
|
||||
table: "Permissions",
|
||||
type: "TEXT",
|
||||
maxLength: 36,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Tokens",
|
||||
table: "Tokens",
|
||||
column: "Token");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Tokens",
|
||||
table: "Tokens");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "Permissions");
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "Id",
|
||||
table: "Users",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldMaxLength: 36)
|
||||
.Annotation("Sqlite:Autoincrement", true);
|
||||
|
||||
migrationBuilder.AlterColumn<long>(
|
||||
name: "UserId",
|
||||
table: "Tokens",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldMaxLength: 36);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "Id",
|
||||
table: "Tokens",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L)
|
||||
.Annotation("Sqlite:Autoincrement", true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "OwnerId",
|
||||
table: "Permissions",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Tokens",
|
||||
table: "Tokens",
|
||||
column: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
97
DatabaseTest/Migrations/DatabaseContextModelSnapshot.cs
Normal file
97
DatabaseTest/Migrations/DatabaseContextModelSnapshot.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DatabaseTest;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DatabaseTest.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
partial class DatabaseContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.PermissionEntry", b =>
|
||||
{
|
||||
b.Property<long>("RecordId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("GrantedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PermissionText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("RecordId");
|
||||
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.TokenEntry", b =>
|
||||
{
|
||||
b.Property<string>("Token")
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasMaxLength(1)
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Token");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HopFrame.Database.Models.Entries.UserEntry", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
60
DatabaseTest/Program.cs
Normal file
60
DatabaseTest/Program.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using DatabaseTest;
|
||||
using HopFrame.Api;
|
||||
using HopFrame.Api.Controller;
|
||||
using HopFrame.Security.Authentication;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddController<SecurityController<DatabaseContext>>();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddDbContext<DatabaseContext>();
|
||||
builder.Services.AddHopFrameAuthentication<DatabaseContext>();
|
||||
//builder.Logging.AddFilter<HopFrameAuthentication<DatabaseContext>>(options => options == LogLevel.None);
|
||||
|
||||
builder.Services.AddSwaggerGen(c => {
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
|
||||
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
|
||||
Enter 'Bearer' [space] and then your token in the text input below.",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement {{
|
||||
new OpenApiSecurityScheme {
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
},
|
||||
Scheme = "oauth2",
|
||||
Name = "Bearer",
|
||||
In = ParameterLocation.Header,
|
||||
},
|
||||
ArraySegment<string>.Empty
|
||||
}});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment()) {
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
//app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
41
DatabaseTest/Properties/launchSettings.json
Normal file
41
DatabaseTest/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:19326",
|
||||
"sslPort": 44320
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5158",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7283;http://localhost:5158",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
DatabaseTest/appsettings.json
Normal file
10
DatabaseTest/appsettings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"HopFrame.Security.Authentication.HopFrameAuthentication": "None"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
178
HopFrame.Api/Controller/SecurityController.cs
Normal file
178
HopFrame.Api/Controller/SecurityController.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using HopFrame.Api.Logic;
|
||||
using HopFrame.Api.Models;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using HopFrame.Security.Authentication;
|
||||
using HopFrame.Security.Authorization;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Api.Controller;
|
||||
|
||||
[ApiController]
|
||||
[Route("authentication")]
|
||||
public class SecurityController<TDbContext>(TDbContext context) : ControllerBase where TDbContext : HopDbContextBase {
|
||||
|
||||
private const string RefreshTokenType = "HopFrame.Security.RefreshToken";
|
||||
|
||||
[HttpPut("login")]
|
||||
public async Task<ILogicResult<SingleValueResult<string>>> Login([FromBody] UserLogin login) {
|
||||
var user = await context.Users.SingleOrDefaultAsync(user => user.Email == login.Email);
|
||||
|
||||
if (user is null)
|
||||
return LogicResult<SingleValueResult<string>>.NotFound("The provided email address was not found");
|
||||
|
||||
var hashedPassword = EncryptionManager.Hash(login.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
if (hashedPassword != user.Password)
|
||||
return LogicResult<SingleValueResult<string>>.Forbidden("The provided password is not correct");
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
|
||||
await context.Tokens.AddRangeAsync(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<ILogicResult<SingleValueResult<string>>> Register([FromBody] UserRegister register) {
|
||||
//TODO: Validate Password requirements
|
||||
|
||||
if (await context.Users.AnyAsync(user => user.Username == register.Username || user.Email == register.Email))
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Username or Email is already registered");
|
||||
|
||||
var user = new UserEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Email = register.Email,
|
||||
Username = register.Username,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
};
|
||||
user.Password = EncryptionManager.Hash(register.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
await context.Users.AddAsync(user);
|
||||
|
||||
var refreshToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.RefreshTokenType,
|
||||
UserId = user.Id
|
||||
};
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
HttpContext.Response.Cookies.Append(RefreshTokenType, refreshToken.Token, new CookieOptions {
|
||||
MaxAge = HopFrameAuthentication<TDbContext>.RefreshTokenTime,
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
|
||||
await context.Tokens.AddRangeAsync(refreshToken, accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
}
|
||||
|
||||
[HttpGet("authenticate")]
|
||||
public async Task<ILogicResult<SingleValueResult<string>>> Authenticate() {
|
||||
var refreshToken = HttpContext.Request.Cookies[RefreshTokenType];
|
||||
|
||||
if (string.IsNullOrEmpty(refreshToken))
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token not provided");
|
||||
|
||||
var token = await context.Tokens.SingleOrDefaultAsync(token => token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType);
|
||||
|
||||
if (token is null)
|
||||
return LogicResult<SingleValueResult<string>>.NotFound("Refresh token not valid");
|
||||
|
||||
if (token.CreatedAt + HopFrameAuthentication<TDbContext>.RefreshTokenTime < DateTime.Now)
|
||||
return LogicResult<SingleValueResult<string>>.Conflict("Refresh token is expired");
|
||||
|
||||
var accessToken = new TokenEntry {
|
||||
CreatedAt = DateTime.Now,
|
||||
Token = Guid.NewGuid().ToString(),
|
||||
Type = TokenEntry.AccessTokenType,
|
||||
UserId = token.UserId
|
||||
};
|
||||
|
||||
await context.Tokens.AddAsync(accessToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return LogicResult<SingleValueResult<string>>.Ok(accessToken.Token);
|
||||
}
|
||||
|
||||
[HttpDelete("logout"), Authorized]
|
||||
public async Task<ILogicResult> Logout() {
|
||||
var accessToken = HttpContext.User.GetAccessTokenId();
|
||||
var refreshToken = HttpContext.Request.Cookies[RefreshTokenType];
|
||||
|
||||
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(refreshToken))
|
||||
return LogicResult.Conflict("access or refresh token not provided");
|
||||
|
||||
var tokenEntries = await context.Tokens.Where(token =>
|
||||
(token.Token == accessToken && token.Type == TokenEntry.AccessTokenType) ||
|
||||
(token.Token == refreshToken && token.Type == TokenEntry.RefreshTokenType))
|
||||
.ToArrayAsync();
|
||||
|
||||
if (tokenEntries.Length != 2)
|
||||
return LogicResult.NotFound("One or more of the provided tokens was not found");
|
||||
|
||||
context.Tokens.Remove(tokenEntries[0]);
|
||||
context.Tokens.Remove(tokenEntries[1]);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
HttpContext.Response.Cookies.Delete(RefreshTokenType);
|
||||
|
||||
return LogicResult.Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("delete"), Authorized]
|
||||
public async Task<ILogicResult> Delete([FromBody] UserLogin login) {
|
||||
var token = HttpContext.User.GetAccessTokenId();
|
||||
var userId = (await context.Tokens.SingleOrDefaultAsync(t => t.Token == token && t.Type == TokenEntry.AccessTokenType))?.UserId;
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
return LogicResult.NotFound("Access token does not match any user");
|
||||
|
||||
var user = await context.Users.SingleAsync(user => user.Id == userId);
|
||||
|
||||
var password = EncryptionManager.Hash(login.Password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
|
||||
if (user.Password != password)
|
||||
return LogicResult.Forbidden("The provided password is not correct");
|
||||
|
||||
var tokens = await context.Tokens.Where(t => t.UserId == userId).ToArrayAsync();
|
||||
|
||||
context.Tokens.RemoveRange(tokens);
|
||||
context.Users.Remove(user);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
HttpContext.Response.Cookies.Delete(RefreshTokenType);
|
||||
|
||||
return LogicResult.Ok();
|
||||
}
|
||||
|
||||
}
|
||||
12
HopFrame.Api/ControllerExtensions.cs
Normal file
12
HopFrame.Api/ControllerExtensions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace HopFrame.Api;
|
||||
|
||||
public static class ControllerExtensions {
|
||||
|
||||
public static IMvcBuilder AddController<TController>(this IMvcBuilder builder) where TController : ControllerBase {
|
||||
return builder.AddApplicationPart(typeof(TController).Assembly);
|
||||
}
|
||||
|
||||
}
|
||||
17
HopFrame.Api/EncryptionManager.cs
Normal file
17
HopFrame.Api/EncryptionManager.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
|
||||
namespace HopFrame.Api;
|
||||
|
||||
public static class EncryptionManager {
|
||||
|
||||
public static string Hash(string input, byte[] salt, KeyDerivationPrf method = KeyDerivationPrf.HMACSHA256) {
|
||||
return Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: input,
|
||||
salt: salt,
|
||||
prf: method,
|
||||
iterationCount: 100000,
|
||||
numBytesRequested: 256 / 8
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
15
HopFrame.Api/HopFrame.Api.csproj
Normal file
15
HopFrame.Api/HopFrame.Api.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HopFrame.Database\HopFrame.Database.csproj" />
|
||||
<ProjectReference Include="..\HopFrame.Security\HopFrame.Security.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
50
HopFrame.Api/Logic/ControllerBaseExtension.cs
Normal file
50
HopFrame.Api/Logic/ControllerBaseExtension.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HopFrame.Api.Logic;
|
||||
|
||||
public static class ControllerBaseExtension {
|
||||
public static ActionResult FromLogicResult(this ControllerBase controller, ILogicResult result) {
|
||||
switch (result.State) {
|
||||
case LogicResultState.Ok:
|
||||
return controller.Ok();
|
||||
|
||||
case LogicResultState.BadRequest:
|
||||
return controller.StatusCode((int)HttpStatusCode.BadRequest, result.Message);
|
||||
|
||||
case LogicResultState.Forbidden:
|
||||
return controller.StatusCode((int)HttpStatusCode.Forbidden, result.Message);
|
||||
|
||||
case LogicResultState.NotFound:
|
||||
return controller.StatusCode((int)HttpStatusCode.NotFound, result.Message);
|
||||
|
||||
case LogicResultState.Conflict:
|
||||
return controller.StatusCode((int)HttpStatusCode.Conflict, result.Message);
|
||||
|
||||
default:
|
||||
throw new Exception("An unhandled result has occurred as a result of a service call.");
|
||||
}
|
||||
}
|
||||
|
||||
public static ActionResult FromLogicResult<T>(this ControllerBase controller, ILogicResult<T> result) {
|
||||
switch (result.State) {
|
||||
case LogicResultState.Ok:
|
||||
return controller.Ok(result.Data);
|
||||
|
||||
case LogicResultState.BadRequest:
|
||||
return controller.StatusCode((int)HttpStatusCode.BadRequest, result.Message);
|
||||
|
||||
case LogicResultState.Forbidden:
|
||||
return controller.StatusCode((int)HttpStatusCode.Forbidden, result.Message);
|
||||
|
||||
case LogicResultState.NotFound:
|
||||
return controller.StatusCode((int)HttpStatusCode.NotFound, result.Message);
|
||||
|
||||
case LogicResultState.Conflict:
|
||||
return controller.StatusCode((int)HttpStatusCode.Conflict, result.Message);
|
||||
|
||||
default:
|
||||
throw new Exception("An unhandled result has occurred as a result of a service call.");
|
||||
}
|
||||
}
|
||||
}
|
||||
19
HopFrame.Api/Logic/ILogicResult.cs
Normal file
19
HopFrame.Api/Logic/ILogicResult.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace HopFrame.Api.Logic;
|
||||
|
||||
public interface ILogicResult {
|
||||
LogicResultState State { get; set; }
|
||||
|
||||
string Message { get; set; }
|
||||
|
||||
bool IsSuccessful { get; }
|
||||
}
|
||||
|
||||
public interface ILogicResult<T> {
|
||||
LogicResultState State { get; set; }
|
||||
|
||||
T Data { get; set; }
|
||||
|
||||
string Message { get; set; }
|
||||
|
||||
bool IsSuccessful { get; }
|
||||
}
|
||||
170
HopFrame.Api/Logic/LogicResult.cs
Normal file
170
HopFrame.Api/Logic/LogicResult.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
namespace HopFrame.Api.Logic;
|
||||
|
||||
public class LogicResult : ILogicResult {
|
||||
public LogicResultState State { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public bool IsSuccessful => State == LogicResultState.Ok;
|
||||
|
||||
public static LogicResult Ok() {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.Ok
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult BadRequest() {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.BadRequest
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult BadRequest(string message) {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.BadRequest,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Forbidden() {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.Forbidden
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Forbidden(string message) {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.Forbidden,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult NotFound() {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult NotFound(string message) {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.NotFound,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Conflict() {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.Conflict
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Conflict(string message) {
|
||||
return new LogicResult() {
|
||||
State = LogicResultState.Conflict,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Forward(LogicResult result) {
|
||||
return new LogicResult() {
|
||||
State = result.State,
|
||||
Message = result.Message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult Forward<T>(ILogicResult<T> result) {
|
||||
return new LogicResult() {
|
||||
State = result.State,
|
||||
Message = result.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class LogicResult<T> : ILogicResult<T> {
|
||||
public LogicResultState State { get; set; }
|
||||
|
||||
public T Data { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public bool IsSuccessful => State == LogicResultState.Ok;
|
||||
|
||||
public static LogicResult<T> Ok() {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Ok
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Ok(T result) {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Ok,
|
||||
Data = result
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> BadRequest() {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.BadRequest
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> BadRequest(string message) {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.BadRequest,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Forbidden() {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Forbidden
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Forbidden(string message) {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Forbidden,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> NotFound() {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> NotFound(string message) {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.NotFound,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Conflict() {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Conflict
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Conflict(string message) {
|
||||
return new LogicResult<T>() {
|
||||
State = LogicResultState.Conflict,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Forward(ILogicResult result) {
|
||||
return new LogicResult<T>() {
|
||||
State = result.State,
|
||||
Message = result.Message
|
||||
};
|
||||
}
|
||||
|
||||
public static LogicResult<T> Forward<T2>(ILogicResult<T2> result) {
|
||||
return new LogicResult<T>() {
|
||||
State = result.State,
|
||||
Message = result.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
9
HopFrame.Api/Logic/LogicResultState.cs
Normal file
9
HopFrame.Api/Logic/LogicResultState.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace HopFrame.Api.Logic;
|
||||
|
||||
public enum LogicResultState {
|
||||
Ok,
|
||||
BadRequest,
|
||||
Forbidden,
|
||||
NotFound,
|
||||
Conflict
|
||||
}
|
||||
13
HopFrame.Api/Models/SingleValueResult.cs
Normal file
13
HopFrame.Api/Models/SingleValueResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace HopFrame.Api.Models;
|
||||
|
||||
public struct SingleValueResult<T>(T value) {
|
||||
public T Value { get; set; } = value;
|
||||
|
||||
public static implicit operator T(SingleValueResult<T> v) {
|
||||
return v.Value;
|
||||
}
|
||||
|
||||
public static implicit operator SingleValueResult<T>(T v) {
|
||||
return new SingleValueResult<T>(v);
|
||||
}
|
||||
}
|
||||
6
HopFrame.Api/Models/UserLogin.cs
Normal file
6
HopFrame.Api/Models/UserLogin.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace HopFrame.Api.Models;
|
||||
|
||||
public struct UserLogin {
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
7
HopFrame.Api/Models/UserRegister.cs
Normal file
7
HopFrame.Api/Models/UserRegister.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace HopFrame.Api.Models;
|
||||
|
||||
public struct UserRegister {
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
22
HopFrame.Database/HopDbContextBase.cs
Normal file
22
HopFrame.Database/HopDbContextBase.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using HopFrame.Database.Models.Entries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HopFrame.Database;
|
||||
|
||||
public class HopDbContextBase : DbContext {
|
||||
|
||||
public HopDbContextBase() {}
|
||||
|
||||
public HopDbContextBase(DbContextOptions options) : base(options) {}
|
||||
|
||||
public virtual DbSet<UserEntry> Users { get; set; }
|
||||
public virtual DbSet<PermissionEntry> Permissions { get; set; }
|
||||
public virtual DbSet<TokenEntry> Tokens { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<UserEntry>();
|
||||
modelBuilder.Entity<PermissionEntry>();
|
||||
}
|
||||
}
|
||||
14
HopFrame.Database/HopFrame.Database.csproj
Normal file
14
HopFrame.Database/HopFrame.Database.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
HopFrame.Database/Models/Entries/PermissionEntry.cs
Normal file
18
HopFrame.Database/Models/Entries/PermissionEntry.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public sealed class PermissionEntry {
|
||||
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public long RecordId { get; set; }
|
||||
|
||||
[Required, MaxLength(255)]
|
||||
public string PermissionText { get; set; }
|
||||
|
||||
[Required, MinLength(36), MaxLength(36)]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime GrantedAt { get; set; }
|
||||
}
|
||||
25
HopFrame.Database/Models/Entries/TokenEntry.cs
Normal file
25
HopFrame.Database/Models/Entries/TokenEntry.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public class TokenEntry {
|
||||
public const int RefreshTokenType = 0;
|
||||
public const int AccessTokenType = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Type of the stored Token
|
||||
/// 0: Refresh token
|
||||
/// 1: Access token
|
||||
/// </summary>
|
||||
[Required, MinLength(1), MaxLength(1)]
|
||||
public int Type { get; set; }
|
||||
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public string Token { get; set; }
|
||||
|
||||
[Required, MinLength(36), MaxLength(36)]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
20
HopFrame.Database/Models/Entries/UserEntry.cs
Normal file
20
HopFrame.Database/Models/Entries/UserEntry.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HopFrame.Database.Models.Entries;
|
||||
|
||||
public class UserEntry {
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public string Id { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required, MaxLength(50), EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required, MinLength(8), MaxLength(255)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
23
HopFrame.Database/Models/ModelExtensions.cs
Normal file
23
HopFrame.Database/Models/ModelExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using HopFrame.Database.Models.Entries;
|
||||
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public static class ModelExtensions {
|
||||
|
||||
public static User ToUserModel(this UserEntry entry, HopDbContextBase contextBase) {
|
||||
var user = new User {
|
||||
Id = Guid.Parse(entry.Id),
|
||||
Username = entry.Username,
|
||||
Email = entry.Email,
|
||||
CreatedAt = entry.CreatedAt
|
||||
};
|
||||
|
||||
user.Permissions = contextBase.Permissions
|
||||
.Where(perm => perm.UserId == entry.Id)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToList();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
9
HopFrame.Database/Models/User.cs
Normal file
9
HopFrame.Database/Models/User.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace HopFrame.Database.Models;
|
||||
|
||||
public class User {
|
||||
public Guid Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public IList<string> Permissions { get; set; }
|
||||
}
|
||||
56
HopFrame.Security/Authentication/HopFrameAuthentication.cs
Normal file
56
HopFrame.Security/Authentication/HopFrameAuthentication.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
namespace HopFrame.Security.Authentication;
|
||||
|
||||
public class HopFrameAuthentication<TDbContext> : AuthenticationHandler<AuthenticationSchemeOptions> where TDbContext : HopDbContextBase {
|
||||
|
||||
public const string SchemeName = "HopCore.Authentication";
|
||||
public static readonly TimeSpan AccessTokenTime = new(0, 0, 5, 0);
|
||||
public static readonly TimeSpan RefreshTokenTime = new(30, 0, 0, 0);
|
||||
|
||||
private readonly TDbContext _context;
|
||||
|
||||
public HopFrameAuthentication(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger,
|
||||
UrlEncoder encoder, ISystemClock clock, TDbContext context) : base(options, logger, encoder, clock) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() {
|
||||
var accessToken = Request.Headers["Authorization"].ToString();
|
||||
if (string.IsNullOrEmpty(accessToken)) return AuthenticateResult.Fail("No Access Token provided");
|
||||
|
||||
var tokenEntry = await _context.Tokens.SingleOrDefaultAsync(token => token.Token == accessToken);
|
||||
|
||||
if (tokenEntry is null) return AuthenticateResult.Fail("The provided Access Token does not exist");
|
||||
if (tokenEntry.CreatedAt + AccessTokenTime < DateTime.Now) return AuthenticateResult.Fail("The provided Access Token is expired");
|
||||
|
||||
if (!(await _context.Users.AnyAsync(user => user.Id == tokenEntry.UserId)))
|
||||
return AuthenticateResult.Fail("The provided Access Token does not match any user");
|
||||
|
||||
var claims = new List<Claim> {
|
||||
new(HopFrameClaimTypes.AccessTokenId, accessToken),
|
||||
new(HopFrameClaimTypes.UserId, tokenEntry.UserId.ToString())
|
||||
};
|
||||
|
||||
var permissions = await _context.Permissions
|
||||
.Where(perm => perm.UserId == tokenEntry.UserId)
|
||||
.Select(perm => perm.PermissionText)
|
||||
.ToListAsync();
|
||||
|
||||
claims.AddRange(permissions.Select(perm => new Claim(HopFrameClaimTypes.Permission, perm)));
|
||||
|
||||
var principal = new ClaimsPrincipal();
|
||||
principal.AddIdentity(new ClaimsIdentity(claims, SchemeName));
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using HopFrame.Database;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace HopFrame.Security.Authentication;
|
||||
|
||||
public static class HopFrameAuthenticationExtensions {
|
||||
|
||||
public static AuthenticationBuilder AddHopFrameAuthentication<TDbContext>(this IServiceCollection service) where TDbContext : HopDbContextBase {
|
||||
return service.AddAuthentication(HopFrameAuthentication<TDbContext>.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication<TDbContext>>(HopFrameAuthentication<TDbContext>.SchemeName, _ => {});
|
||||
}
|
||||
|
||||
}
|
||||
9
HopFrame.Security/Authorization/AuthorizedAttribute.cs
Normal file
9
HopFrame.Security/Authorization/AuthorizedAttribute.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public class AuthorizedAttribute : TypeFilterAttribute {
|
||||
public AuthorizedAttribute(params string[] permission) : base(typeof(AuthorizedFilter)) {
|
||||
Arguments = new object[] { permission };
|
||||
}
|
||||
}
|
||||
24
HopFrame.Security/Authorization/AuthorizedFilter.cs
Normal file
24
HopFrame.Security/Authorization/AuthorizedFilter.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace HopFrame.Security.Authorization;
|
||||
|
||||
public class AuthorizedFilter : IAuthorizationFilter {
|
||||
private readonly string[] _permissions;
|
||||
|
||||
public AuthorizedFilter(params string[] permissions) {
|
||||
_permissions = permissions;
|
||||
}
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context) {
|
||||
if (context.Filters.Any(item => item is IAllowAnonymousFilter)) return;
|
||||
|
||||
if (context.HttpContext.User.Identity?.IsAuthenticated == false) {
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO: Check Permissions
|
||||
}
|
||||
}
|
||||
21
HopFrame.Security/Claims/HopFrameClaimTypes.cs
Normal file
21
HopFrame.Security/Claims/HopFrameClaimTypes.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public static class HopFrameClaimTypes {
|
||||
public const string AccessTokenId = "HopFrame.AccessTokenId";
|
||||
public const string UserId = "HopFrame.UserId";
|
||||
public const string Permission = "HopFrame.Permission";
|
||||
|
||||
public static string GetAccessTokenId(this ClaimsPrincipal principal) {
|
||||
return principal.FindFirstValue(AccessTokenId);
|
||||
}
|
||||
|
||||
public static string GetUserId(this ClaimsPrincipal principal) {
|
||||
return principal.FindFirstValue(UserId);
|
||||
}
|
||||
|
||||
public static string[] GetPermissions(this ClaimsPrincipal principal) {
|
||||
return principal.FindAll(Permission).Select(claim => claim.Value).ToArray();
|
||||
}
|
||||
}
|
||||
9
HopFrame.Security/Claims/ITokenContextBase.cs
Normal file
9
HopFrame.Security/Claims/ITokenContextBase.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using HopFrame.Database.Models;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public interface ITokenContextBase {
|
||||
bool IsAuthenticated { get; }
|
||||
User User { get; }
|
||||
Guid AccessToken { get; }
|
||||
}
|
||||
23
HopFrame.Security/Claims/TokenContextImplementor.cs
Normal file
23
HopFrame.Security/Claims/TokenContextImplementor.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using HopFrame.Database;
|
||||
using HopFrame.Database.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace HopFrame.Security.Claims;
|
||||
|
||||
public class TokenContextImplementor : ITokenContextBase {
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly HopDbContextBase _context;
|
||||
|
||||
public TokenContextImplementor(IHttpContextAccessor accessor, HopDbContextBase context) {
|
||||
_accessor = accessor;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public bool IsAuthenticated => _accessor.HttpContext?.User.Identity?.IsAuthenticated == true;
|
||||
|
||||
public User User => _context.Users
|
||||
.SingleOrDefault(user => user.Id == _accessor.HttpContext.User.GetUserId())?
|
||||
.ToUserModel(_context);
|
||||
|
||||
public Guid AccessToken => Guid.Parse(_accessor.HttpContext?.User.GetAccessTokenId() ?? string.Empty);
|
||||
}
|
||||
20
HopFrame.Security/HopFrame.Security.csproj
Normal file
20
HopFrame.Security/HopFrame.Security.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<RootNamespace>HopFrame.Security</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HopFrame.Database\HopFrame.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
39
HopFrame.sln
Normal file
39
HopFrame.sln
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "HopFrame.Database\HopFrame.Database.csproj", "{003120AE-F38B-4632-8497-BE4505189627}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{58703056-8DAD-4221-BBE3-42425D2F4929}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseTest", "DatabaseTest\DatabaseTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Api", "HopFrame.Api\HopFrame.Api.csproj", "{1E821490-AEDC-4F55-B758-52F4FADAB53A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{003120AE-F38B-4632-8497-BE4505189627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{003120AE-F38B-4632-8497-BE4505189627}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{003120AE-F38B-4632-8497-BE4505189627}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{003120AE-F38B-4632-8497-BE4505189627}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F82E1C6-4A42-4337-9E03-2EE6429D004F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F82E1C6-4A42-4337-9E03-2EE6429D004F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F82E1C6-4A42-4337-9E03-2EE6429D004F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F82E1C6-4A42-4337-9E03-2EE6429D004F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1E821490-AEDC-4F55-B758-52F4FADAB53A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E821490-AEDC-4F55-B758-52F4FADAB53A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E821490-AEDC-4F55-B758-52F4FADAB53A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E821490-AEDC-4F55-B758-52F4FADAB53A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF} = {58703056-8DAD-4221-BBE3-42425D2F4929}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
4
HopFrame.sln.DotSettings.user
Normal file
4
HopFrame.sln.DotSettings.user
Normal file
@@ -0,0 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue"><AssemblyExplorer>
|
||||
<Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" />
|
||||
</AssemblyExplorer></s:String></wpf:ResourceDictionary>
|
||||
7
global.json
Normal file
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user