This commit is contained in:
admin 2025-08-09 13:37:34 +02:00
commit 07531425f2
42 changed files with 1199 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
.idea
.vscode
ApplicationHub/bin
ApplicationHub/obj
ApplicationHub/*.db
ApplicationHub.Data.EF/bin
ApplicationHub.Data.EF/obj
ApplicationHub.Data.EF/*.db
ApplicationHub.Data.InMemory/bin
ApplicationHub.Data.InMemory/obj
ApplicationHub.Domain/bin
ApplicationHub.Domain/obj
ApplicationHub.Domain.Contracts/bin
ApplicationHub.Domain.Contracts/obj

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="9.0.8" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
using ApplicationHub.Data.EF.Authentication.Entities;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Data.EF.Authentication;
public class AuthenticationDataContext(DbContextOptions options) : DbContext(options)
{
public DbSet<UserEntity> Users { get; set; }
public DbSet<GroupEntity> Groups { get; set; }
public DbSet<RightEntity> Rights { get; set; }
}

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace ApplicationHub.Data.EF.Authentication;
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<AuthenticationDataContext>
{
public AuthenticationDataContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<AuthenticationDataContext>();
optionsBuilder.UseSqlite("Data Source=Authentication.db");
return new AuthenticationDataContext(optionsBuilder.Options);
}
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.EF.Authentication.Entities;
public class GroupEntity
{
[Key]
public Guid Id { get; set; }
[MaxLength(255)]
public required string Name { get; set; }
public List<RightEntity>? Rights { get; set; }
public List<UserEntity>? Users { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.EF.Authentication.Entities;
public class RightEntity
{
[Key]
public Guid Id { get; set; }
[MaxLength(255)]
public required string Name { get; set; }
public List<GroupEntity>? Groups { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.EF.Authentication.Entities;
public class UserEntity
{
public Guid Id { get; set; }
[MaxLength(255)]
public required string UserName { get; set; }
[MaxLength(255)]
public required string Email { get; set; }
[MaxLength(255)]
public string? PasswordHash { get; set; }
public bool Active { get; set; }
public List<GroupEntity>? Groups { get; set; }
}

View File

@ -0,0 +1,20 @@
using ApplicationHub.Domain.Contracts.Authentication;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Data.EF.Authentication.Repositories;
public class UserRepository(AuthenticationDataContext authenticationDataContext, IMapper mapper) : IUserRepository
{
public User? GetUserByUserName(string userName)
{
return mapper.Map<List<User>>(
authenticationDataContext.Users
.Include(x => x.Groups)
.Where(x => x.UserName == userName && x.Active)
.ToList()
)
.FirstOrDefault();
}
}

View File

@ -0,0 +1,20 @@
using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
namespace ApplicationHub.Data.EF.Configuration;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<User, UserEntity>();
CreateMap<UserEntity, User>();
CreateMap<Group, GroupEntity>();
CreateMap<GroupEntity, Group>();
CreateMap<Right, RightEntity>();
CreateMap<RightEntity, Right>();
}
}

View File

@ -0,0 +1,145 @@
// <auto-generated />
using System;
using ApplicationHub.Data.EF.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace ApplicationHub.Data.EF.Migrations
{
[DbContext(typeof(AuthenticationDataContext))]
[Migration("20250809103237_InitDB")]
partial class InitDB
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Groups");
});
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Rights");
});
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("GroupEntityRightEntity", b =>
{
b.Property<Guid>("GroupsId")
.HasColumnType("TEXT");
b.Property<Guid>("RightsId")
.HasColumnType("TEXT");
b.HasKey("GroupsId", "RightsId");
b.HasIndex("RightsId");
b.ToTable("GroupEntityRightEntity");
});
modelBuilder.Entity("GroupEntityUserEntity", b =>
{
b.Property<Guid>("GroupsId")
.HasColumnType("TEXT");
b.Property<Guid>("UsersId")
.HasColumnType("TEXT");
b.HasKey("GroupsId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("GroupEntityUserEntity");
});
modelBuilder.Entity("GroupEntityRightEntity", b =>
{
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", null)
.WithMany()
.HasForeignKey("RightsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupEntityUserEntity", b =>
{
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,158 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ApplicationHub.Data.EF.Migrations
{
/// <inheritdoc />
public partial class InitDB : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Groups",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Groups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Rights",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Rights", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
UserName = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
Email = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
PasswordHash = table.Column<string>(type: "TEXT", maxLength: 255, nullable: true),
Active = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "GroupEntityRightEntity",
columns: table => new
{
GroupsId = table.Column<Guid>(type: "TEXT", nullable: false),
RightsId = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GroupEntityRightEntity", x => new { x.GroupsId, x.RightsId });
table.ForeignKey(
name: "FK_GroupEntityRightEntity_Groups_GroupsId",
column: x => x.GroupsId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_GroupEntityRightEntity_Rights_RightsId",
column: x => x.RightsId,
principalTable: "Rights",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "GroupEntityUserEntity",
columns: table => new
{
GroupsId = table.Column<Guid>(type: "TEXT", nullable: false),
UsersId = table.Column<Guid>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GroupEntityUserEntity", x => new { x.GroupsId, x.UsersId });
table.ForeignKey(
name: "FK_GroupEntityUserEntity_Groups_GroupsId",
column: x => x.GroupsId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_GroupEntityUserEntity_Users_UsersId",
column: x => x.UsersId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_GroupEntityRightEntity_RightsId",
table: "GroupEntityRightEntity",
column: "RightsId");
migrationBuilder.CreateIndex(
name: "IX_GroupEntityUserEntity_UsersId",
table: "GroupEntityUserEntity",
column: "UsersId");
var userAdminId = Guid.NewGuid();
var groupAdminId = Guid.NewGuid();
var groupUserId = Guid.NewGuid();
var groupGuestId = Guid.NewGuid();
var readRightId = Guid.NewGuid();
var writeRightId = Guid.NewGuid();
var deleteRightId = Guid.NewGuid();
migrationBuilder.InsertData("Rights", ["Id", "Name"], [readRightId, "READ"]);
migrationBuilder.InsertData("Rights", ["Id", "Name"], [writeRightId, "WRITE"]);
migrationBuilder.InsertData("Rights", ["Id", "Name"], [deleteRightId, "DELETE"]);
migrationBuilder.InsertData("Groups", ["Id", "Name"], [groupAdminId, "Group_Name_Administrator"]);
migrationBuilder.InsertData("Groups", ["Id", "Name"], [groupUserId, "Group_Name_User"]);
migrationBuilder.InsertData("Groups", ["Id", "Name"], [groupGuestId, "Group_Name_Guest"]);
migrationBuilder.InsertData("Users", ["Id", "UserName", "Email", "PasswordHash", "Active"], [userAdminId, "admin", "admin@example.de", "AQAAAAIAAYagAAAAEFTsFQQz1Yp6d2zw5iQ4AzNhQQ7QuvdhK3Y2kN/7wp97OXgB2fTrabYqGDYdFSMMkQ==", true]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupAdminId, readRightId]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupAdminId, writeRightId]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupAdminId, deleteRightId]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupUserId, readRightId]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupUserId, writeRightId]);
migrationBuilder.InsertData("GroupEntityRightEntity", ["GroupsId", "RightsId"], [groupGuestId, readRightId]);
migrationBuilder.InsertData("GroupEntityUserEntity", ["GroupsId", "UsersId"], [groupAdminId, userAdminId]);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GroupEntityRightEntity");
migrationBuilder.DropTable(
name: "GroupEntityUserEntity");
migrationBuilder.DropTable(
name: "Rights");
migrationBuilder.DropTable(
name: "Groups");
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@ -0,0 +1,142 @@
// <auto-generated />
using System;
using ApplicationHub.Data.EF.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace ApplicationHub.Data.EF.Migrations
{
[DbContext(typeof(AuthenticationDataContext))]
partial class AuthenticationDataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Groups");
});
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Rights");
});
modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("GroupEntityRightEntity", b =>
{
b.Property<Guid>("GroupsId")
.HasColumnType("TEXT");
b.Property<Guid>("RightsId")
.HasColumnType("TEXT");
b.HasKey("GroupsId", "RightsId");
b.HasIndex("RightsId");
b.ToTable("GroupEntityRightEntity");
});
modelBuilder.Entity("GroupEntityUserEntity", b =>
{
b.Property<Guid>("GroupsId")
.HasColumnType("TEXT");
b.Property<Guid>("UsersId")
.HasColumnType("TEXT");
b.HasKey("GroupsId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("GroupEntityUserEntity");
});
modelBuilder.Entity("GroupEntityRightEntity", b =>
{
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", null)
.WithMany()
.HasForeignKey("RightsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupEntityUserEntity", b =>
{
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.GroupEntity", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.InMemory.Authentication.Entities;
public class GroupDto
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = "";
public List<RightDto> Rights { get; set; } = new();
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.InMemory.Authentication.Entities;
public class RightDto
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = "";
}

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Data.InMemory.Authentication.Entities;
public class UserDto
{
[Key]
public Guid Id { get; set; }
public string UserName { get; set; } = "";
public string Email { get; set; } = "";
public string PasswordHash { get; set; } = "";
public bool Active { get; set; }
public List<GroupDto> Groups { get; set; } = new();
}

View File

@ -0,0 +1,76 @@
using ApplicationHub.Data.InMemory.Authentication.Entities;
namespace ApplicationHub.Data.InMemory.Authentication;
internal static class RightData
{
public static List<RightDto> Source = new()
{
new RightDto
{
Id = Guid.Parse("5558D82C-B73B-42AD-9454-EF6FAB2E73EB"),
Name = "READ",
},
new RightDto
{
Id = Guid.Parse("5558D82C-B73B-42AD-9454-EF7FAB2E73EB"),
Name = "WRITE",
},
new RightDto
{
Id = Guid.Parse("5558D82C-B74B-42AD-9454-EF6FAB5E73EB"),
Name = "DELETE",
}
};
}
internal static class GroupData
{
public static List<GroupDto> Source = new()
{
new GroupDto
{
Id = Guid.Parse("8888D82C-B73B-42AD-9454-EF6FAB2E73EB"),
Name = "Group_Name_Administrator",
Rights = RightData.Source
},
new GroupDto
{
Id = Guid.Parse("8588D82C-B73B-42AD-9454-EF6FAB2E73EB"),
Name = "Group_Name_User",
Rights = new List<RightDto>
{
RightData.Source.First(x => x.Name == "READ"),
RightData.Source.First(x => x.Name == "WRITE"),
}
},
new GroupDto
{
Id = Guid.Parse("8845D82C-B73B-42AD-9454-EF6FAB2E73EB"),
Name = "Group_Name_Guest",
Rights = new List<RightDto>
{
RightData.Source.First(x => x.Name == "READ"),
}
}
};
}
internal static class UserData
{
public static List<UserDto> Source = new()
{
new UserDto
{
Id = Guid.Parse("9998D82C-B73B-42AD-9454-EF6FAB2E73EB"),
UserName = "admin",
Email = "admin@example.de",
PasswordHash = "AQAAAAIAAYagAAAAEFTsFQQz1Yp6d2zw5iQ4AzNhQQ7QuvdhK3Y2kN/7wp97OXgB2fTrabYqGDYdFSMMkQ==",
Active = true,
Groups = new List<GroupDto>
{
GroupData.Source.First(x => x.Name == "Group_Name_Administrator"),
}
},
};
}

View File

@ -0,0 +1,17 @@
using ApplicationHub.Domain.Contracts.Authentication;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
namespace ApplicationHub.Data.InMemory.Authentication;
public class UserDataContext(IMapper mapper) : IUserRepository
{
public User? GetUserByUserName(string userName)
{
var result = UserData.Source
.Where(x => x.UserName == userName && x.Active)
.ToList();
return mapper.Map<List<User>>(result).FirstOrDefault();
}
}

View File

@ -0,0 +1,20 @@
using ApplicationHub.Data.InMemory.Authentication.Entities;
using ApplicationHub.Domain.Contracts.Authentication.Models;
using AutoMapper;
namespace ApplicationHub.Data.InMemory.Configuration;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<UserDto, User>();
CreateMap<User, UserDto>();
CreateMap<GroupDto, Group>();
CreateMap<Group, GroupDto>();
CreateMap<RightDto, Right>();
CreateMap<Right, RightDto>();
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
using ApplicationHub.Domain.Contracts.Authentication.Models;
namespace ApplicationHub.Domain.Contracts.Authentication;
public interface IUserRepository
{
public User? GetUserByUserName(string userName);
}

View File

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Domain.Contracts.Authentication.Models;
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = "";
public IEnumerable<Right> Rights { get; set; } = new List<Right>();
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Domain.Contracts.Authentication.Models;
public class Right
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; } = "";
}

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace ApplicationHub.Domain.Contracts.Authentication.Models;
public class User
{
[Key]
public Guid Id { get; set; }
public string UserName { get; set; } = "";
public string Email { get; set; } = "";
public string PasswordHash { get; set; } = "";
public bool Active { get; set; }
public IEnumerable<Group> Groups { get; set; } = new List<Group>();
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Adapter</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
using ApplicationHub.Domain.Contracts.Authentication;
using ApplicationHub.Domain.Contracts.Authentication.Models;
namespace Adapter.Authentication.Services;
public class AuthenticationService(IUserRepository userRepository)
{
public User? GetUserByUserName(string userName)
{
return userRepository.GetUserByUserName(userName);
}
}

40
ApplicationHub.sln Normal file
View File

@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationHub", "ApplicationHub\ApplicationHub.csproj", "{B7069D37-4899-44E1-BD11-2F8C66F49FF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationHub.Domain", "ApplicationHub.Domain\ApplicationHub.Domain.csproj", "{D9E2D5BA-6543-4390-A6B1-C41A5FEED4AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationHub.Data.InMemory", "ApplicationHub.Data.InMemory\ApplicationHub.Data.InMemory.csproj", "{E6D45245-FE75-4654-84DA-E6C630ADBD80}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationHub.Domain.Contracts", "ApplicationHub.Domain.Contracts\ApplicationHub.Domain.Contracts.csproj", "{B81B0BC0-5FE4-4CA8-A28E-75203D08C941}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationHub.Data.EF", "ApplicationHub.Data.EF\ApplicationHub.Data.EF.csproj", "{2E422B18-FBD8-4AEA-83F5-7BD6622D7DC4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B7069D37-4899-44E1-BD11-2F8C66F49FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7069D37-4899-44E1-BD11-2F8C66F49FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7069D37-4899-44E1-BD11-2F8C66F49FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7069D37-4899-44E1-BD11-2F8C66F49FF6}.Release|Any CPU.Build.0 = Release|Any CPU
{D9E2D5BA-6543-4390-A6B1-C41A5FEED4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9E2D5BA-6543-4390-A6B1-C41A5FEED4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9E2D5BA-6543-4390-A6B1-C41A5FEED4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9E2D5BA-6543-4390-A6B1-C41A5FEED4AE}.Release|Any CPU.Build.0 = Release|Any CPU
{E6D45245-FE75-4654-84DA-E6C630ADBD80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6D45245-FE75-4654-84DA-E6C630ADBD80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6D45245-FE75-4654-84DA-E6C630ADBD80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6D45245-FE75-4654-84DA-E6C630ADBD80}.Release|Any CPU.Build.0 = Release|Any CPU
{B81B0BC0-5FE4-4CA8-A28E-75203D08C941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B81B0BC0-5FE4-4CA8-A28E-75203D08C941}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B81B0BC0-5FE4-4CA8-A28E-75203D08C941}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B81B0BC0-5FE4-4CA8-A28E-75203D08C941}.Release|Any CPU.Build.0 = Release|Any CPU
{2E422B18-FBD8-4AEA-83F5-7BD6622D7DC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E422B18-FBD8-4AEA-83F5-7BD6622D7DC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E422B18-FBD8-4AEA-83F5-7BD6622D7DC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E422B18-FBD8-4AEA-83F5-7BD6622D7DC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationHub.Data.EF\ApplicationHub.Data.EF.csproj" />
<ProjectReference Include="..\ApplicationHub.Data.InMemory\ApplicationHub.Data.InMemory.csproj" />
<ProjectReference Include="..\ApplicationHub.Domain\ApplicationHub.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
using ApplicationHub.Data.EF.Authentication.Entities;
using ApplicationHub.Data.EF.Authentication.Repositories;
using ApplicationHub.Data.InMemory.Authentication;
using ApplicationHub.Data.InMemory.Authentication.Entities;
using ApplicationHub.Domain.Contracts.Authentication;
namespace ApplicationHub.Configuration;
public static class AdapterConfiguration
{
/// <summary>
/// Setup the used Adapter for this Application
/// </summary>
/// <param name="builder">the Web Application Builder from ASP .NET</param>
public static void Configure(WebApplicationBuilder builder)
{
// use EF implementation
builder.Services.AddAutoMapper(typeof(UserEntity));
builder.Services.AddScoped<IUserRepository, UserRepository>();
// use InMemory implementation
// builder.Services.AddScoped<IUserRepository, UserDataContext>();
// builder.Services.AddAutoMapper(typeof(UserDto));
}
}

View File

@ -0,0 +1,34 @@
using System.Text;
using ApplicationHub.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
namespace ApplicationHub.Configuration;
public static class AuthenticationConfiguration
{
/// <summary>
/// Enable the JWT Authentication for the ASP .NET Service
/// </summary>
/// <param name="builder">the Web Application Builder from ASP .NET</param>
public static void Configure(WebApplicationBuilder builder)
{
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"] ?? "notSecure"))
};
});
builder.Services.AddAuthorization();
builder.Services.AddSingleton<IPasswordHasher<UserLogin>, PasswordHasher<UserLogin>>();
}
}

View File

@ -0,0 +1,15 @@
using ApplicationHub.Models;
namespace ApplicationHub.Configuration;
public static class ConfigFileConfiguration
{
/// <summary>
/// Add some Configuration Values to the Dependency Injection
/// </summary>
/// <param name="builder">the Web Application Builder from ASP .NET</param>
public static void Configure(WebApplicationBuilder builder)
{
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
}
}

View File

@ -0,0 +1,28 @@
using ApplicationHub.Data.EF.Authentication;
using Microsoft.EntityFrameworkCore;
namespace ApplicationHub.Configuration;
public static class DataContextConfiguration
{
/// <summary>
/// Set the DataContexts for Dependency Injection
/// </summary>
/// <param name="builder">the Web Application Builder from ASP .NET</param>
public static void Configure(WebApplicationBuilder builder)
{
builder.Services.AddDbContext<AuthenticationDataContext>(o => o.UseSqlite(builder.Configuration.GetConnectionString("AuthenticationConnection")));
}
/// <summary>
/// Runs the DB Migrations <br/><br/>
/// Note: please run this after WebApplicationBuilder was build and before the Run of the WebApplication was called!
/// </summary>
/// <param name="app">the builded WebApplication from ASP .NET</param>
public static void Migrate(WebApplication app)
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AuthenticationDataContext>();
db.Database.Migrate();
}
}

View File

@ -0,0 +1,15 @@
using Adapter.Authentication.Services;
namespace ApplicationHub.Configuration;
public static class ServiceConfiguration
{
/// <summary>
/// Add the Services from Domain the the Dependency Injection
/// </summary>
/// <param name="builder">the Web Application Builder from ASP .NET</param>
public static void Configure(WebApplicationBuilder builder)
{
builder.Services.AddScoped<AuthenticationService>();
}
}

View File

@ -0,0 +1,58 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Adapter.Authentication.Services;
using ApplicationHub.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace ApplicationHub.Controllers.Authentication;
[Route("/api/login")]
[ApiController]
public class LoginController(AuthenticationService authenticationService, IOptions<JwtSettings> jwtSettings, IPasswordHasher<UserLogin> passwordHasher)
{
[HttpPost]
public string Login([FromBody] UserLogin login)
{
try
{
var foundUser = authenticationService.GetUserByUserName(login.UserName);
if (foundUser == null ||
passwordHasher.VerifyHashedPassword(login, foundUser.PasswordHash, login.Password) ==
PasswordVerificationResult.Failed)
{
throw new ArgumentOutOfRangeException(nameof(login), $"login password not matches or is invalid {foundUser?.PasswordHash ?? "<null>"}");
}
return GenerateJwtToken(login.UserName);
}
catch (Exception)
{
return "";
}
}
private string GenerateJwtToken(string username)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Value.Key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: jwtSettings.Value.Issuer,
audience: jwtSettings.Value.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(60),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ApplicationHub.Controllers.Message;
[Route("/api/greet")]
[ApiController]
public class GreeterController
{
[HttpGet("{name}")]
public string Greet(string name)
{
return $"Hello, {name}";
}
[Authorize]
[HttpGet("internal")]
public string SecureGreet()
{
return "";
}
}

View File

@ -0,0 +1,8 @@
namespace ApplicationHub.Models;
public class JwtSettings
{
public string Issuer { get; set; } = "";
public string Audience { get; set; } = "";
public string Key { get; set; } = "";
}

View File

@ -0,0 +1,7 @@
namespace ApplicationHub.Models;
public class UserLogin
{
public string UserName { get; set; } = "";
public string Password { get; set; } = "";
}

32
ApplicationHub/Program.cs Normal file
View File

@ -0,0 +1,32 @@
using ApplicationHub.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
ConfigFileConfiguration.Configure(builder);
DataContextConfiguration.Configure(builder);
AuthenticationConfiguration.Configure(builder);
AdapterConfiguration.Configure(builder);
ServiceConfiguration.Configure(builder);
var app = builder.Build();
DataContextConfiguration.Migrate(app);
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7121;http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"AuthenticationConnection": "Data Source=DevAuthentication.db"
},
"JwtSettings": {
"Issuer": "https://deine-api.com",
"Audience": "https://deine-app.com",
"Key": "DeinLangerGeheimerSchluesselFuerJWT"
}
}

View File

@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"AuthenticationConnection": "Data Source=Authentication.db"
},
"JwtSettings": {
"Issuer": "https://deine-api.com",
"Audience": "https://deine-app.com",
"Key": "DeinLangerGeheimerSchluesselFuerJWT"
}
}

0
README.md Normal file
View File