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