From 07531425f2331b1bd3338833aa84de890acbbc45 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 9 Aug 2025 13:37:34 +0200 Subject: [PATCH] Init --- .gitignore | 19 +++ .../ApplicationHub.Data.EF.csproj | 24 +++ .../AuthenticationDataContext.cs | 11 ++ .../DesignTimeDbContextFactory.cs | 14 ++ .../Authentication/Entities/GroupEntity.cs | 13 ++ .../Authentication/Entities/RightEntity.cs | 12 ++ .../Authentication/Entities/UserEntity.cs | 16 ++ .../Repositories/UserRepository.cs | 20 +++ .../Configuration/MappingProfile.cs | 20 +++ .../20250809103237_InitDB.Designer.cs | 145 ++++++++++++++++ .../Migrations/20250809103237_InitDB.cs | 158 ++++++++++++++++++ .../AuthenticationDataContextModelSnapshot.cs | 142 ++++++++++++++++ .../ApplicationHub.Data.InMemory.csproj | 18 ++ .../Authentication/Entities/GroupDto.cs | 11 ++ .../Authentication/Entities/RightDto.cs | 10 ++ .../Authentication/Entities/UserDto.cs | 14 ++ .../Authentication/Sources.cs | 76 +++++++++ .../Authentication/UserDataContext.cs | 17 ++ .../Configuration/MappingProfile.cs | 20 +++ .../ApplicationHub.Domain.Contracts.csproj | 9 + .../Authentication/IUserRepository.cs | 8 + .../Authentication/Models/Group.cs | 11 ++ .../Authentication/Models/Right.cs | 10 ++ .../Authentication/Models/User.cs | 14 ++ .../ApplicationHub.Domain.csproj | 14 ++ .../Services/AuthenticationService.cs | 12 ++ ApplicationHub.sln | 40 +++++ ApplicationHub/ApplicationHub.csproj | 21 +++ .../Configuration/AdapterConfiguration.cs | 25 +++ .../AuthenticationConfiguration.cs | 34 ++++ .../Configuration/ConfigFileConfiguration.cs | 15 ++ .../Configuration/DataContextConfiguration.cs | 28 ++++ .../Configuration/ServiceConfiguration.cs | 15 ++ .../Authentication/LoginController.cs | 58 +++++++ .../Controllers/Message/GreeterController.cs | 22 +++ ApplicationHub/Models/JwtSettings.cs | 8 + ApplicationHub/Models/UserLogin.cs | 7 + ApplicationHub/Program.cs | 32 ++++ ApplicationHub/Properties/launchSettings.json | 23 +++ ApplicationHub/appsettings.Development.json | 16 ++ ApplicationHub/appsettings.json | 17 ++ README.md | 0 42 files changed, 1199 insertions(+) create mode 100644 .gitignore create mode 100644 ApplicationHub.Data.EF/ApplicationHub.Data.EF.csproj create mode 100644 ApplicationHub.Data.EF/Authentication/AuthenticationDataContext.cs create mode 100644 ApplicationHub.Data.EF/Authentication/DesignTimeDbContextFactory.cs create mode 100644 ApplicationHub.Data.EF/Authentication/Entities/GroupEntity.cs create mode 100644 ApplicationHub.Data.EF/Authentication/Entities/RightEntity.cs create mode 100644 ApplicationHub.Data.EF/Authentication/Entities/UserEntity.cs create mode 100644 ApplicationHub.Data.EF/Authentication/Repositories/UserRepository.cs create mode 100644 ApplicationHub.Data.EF/Configuration/MappingProfile.cs create mode 100644 ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.Designer.cs create mode 100644 ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.cs create mode 100644 ApplicationHub.Data.EF/Migrations/AuthenticationDataContextModelSnapshot.cs create mode 100644 ApplicationHub.Data.InMemory/ApplicationHub.Data.InMemory.csproj create mode 100644 ApplicationHub.Data.InMemory/Authentication/Entities/GroupDto.cs create mode 100644 ApplicationHub.Data.InMemory/Authentication/Entities/RightDto.cs create mode 100644 ApplicationHub.Data.InMemory/Authentication/Entities/UserDto.cs create mode 100644 ApplicationHub.Data.InMemory/Authentication/Sources.cs create mode 100644 ApplicationHub.Data.InMemory/Authentication/UserDataContext.cs create mode 100644 ApplicationHub.Data.InMemory/Configuration/MappingProfile.cs create mode 100644 ApplicationHub.Domain.Contracts/ApplicationHub.Domain.Contracts.csproj create mode 100644 ApplicationHub.Domain.Contracts/Authentication/IUserRepository.cs create mode 100644 ApplicationHub.Domain.Contracts/Authentication/Models/Group.cs create mode 100644 ApplicationHub.Domain.Contracts/Authentication/Models/Right.cs create mode 100644 ApplicationHub.Domain.Contracts/Authentication/Models/User.cs create mode 100644 ApplicationHub.Domain/ApplicationHub.Domain.csproj create mode 100644 ApplicationHub.Domain/Authentication/Services/AuthenticationService.cs create mode 100644 ApplicationHub.sln create mode 100644 ApplicationHub/ApplicationHub.csproj create mode 100644 ApplicationHub/Configuration/AdapterConfiguration.cs create mode 100644 ApplicationHub/Configuration/AuthenticationConfiguration.cs create mode 100644 ApplicationHub/Configuration/ConfigFileConfiguration.cs create mode 100644 ApplicationHub/Configuration/DataContextConfiguration.cs create mode 100644 ApplicationHub/Configuration/ServiceConfiguration.cs create mode 100644 ApplicationHub/Controllers/Authentication/LoginController.cs create mode 100644 ApplicationHub/Controllers/Message/GreeterController.cs create mode 100644 ApplicationHub/Models/JwtSettings.cs create mode 100644 ApplicationHub/Models/UserLogin.cs create mode 100644 ApplicationHub/Program.cs create mode 100644 ApplicationHub/Properties/launchSettings.json create mode 100644 ApplicationHub/appsettings.Development.json create mode 100644 ApplicationHub/appsettings.json create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e6cfd2 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/ApplicationHub.Data.EF/ApplicationHub.Data.EF.csproj b/ApplicationHub.Data.EF/ApplicationHub.Data.EF.csproj new file mode 100644 index 0000000..a174a13 --- /dev/null +++ b/ApplicationHub.Data.EF/ApplicationHub.Data.EF.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/ApplicationHub.Data.EF/Authentication/AuthenticationDataContext.cs b/ApplicationHub.Data.EF/Authentication/AuthenticationDataContext.cs new file mode 100644 index 0000000..7bbf11b --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/AuthenticationDataContext.cs @@ -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 Users { get; set; } + public DbSet Groups { get; set; } + public DbSet Rights { get; set; } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Authentication/DesignTimeDbContextFactory.cs b/ApplicationHub.Data.EF/Authentication/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..5ec4b2c --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/DesignTimeDbContextFactory.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace ApplicationHub.Data.EF.Authentication; + +public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public AuthenticationDataContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite("Data Source=Authentication.db"); + return new AuthenticationDataContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Authentication/Entities/GroupEntity.cs b/ApplicationHub.Data.EF/Authentication/Entities/GroupEntity.cs new file mode 100644 index 0000000..1ad847e --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/Entities/GroupEntity.cs @@ -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? Rights { get; set; } + public List? Users { get; set; } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Authentication/Entities/RightEntity.cs b/ApplicationHub.Data.EF/Authentication/Entities/RightEntity.cs new file mode 100644 index 0000000..be3cde7 --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/Entities/RightEntity.cs @@ -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? Groups { get; set; } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Authentication/Entities/UserEntity.cs b/ApplicationHub.Data.EF/Authentication/Entities/UserEntity.cs new file mode 100644 index 0000000..c3aa606 --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/Entities/UserEntity.cs @@ -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? Groups { get; set; } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Authentication/Repositories/UserRepository.cs b/ApplicationHub.Data.EF/Authentication/Repositories/UserRepository.cs new file mode 100644 index 0000000..e1d868a --- /dev/null +++ b/ApplicationHub.Data.EF/Authentication/Repositories/UserRepository.cs @@ -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>( + authenticationDataContext.Users + .Include(x => x.Groups) + .Where(x => x.UserName == userName && x.Active) + .ToList() + ) + .FirstOrDefault(); + } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Configuration/MappingProfile.cs b/ApplicationHub.Data.EF/Configuration/MappingProfile.cs new file mode 100644 index 0000000..5f23c46 --- /dev/null +++ b/ApplicationHub.Data.EF/Configuration/MappingProfile.cs @@ -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(); + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.Designer.cs b/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.Designer.cs new file mode 100644 index 0000000..2b09481 --- /dev/null +++ b/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.Designer.cs @@ -0,0 +1,145 @@ +// +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 + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Rights"); + }); + + modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("GroupEntityRightEntity", b => + { + b.Property("GroupsId") + .HasColumnType("TEXT"); + + b.Property("RightsId") + .HasColumnType("TEXT"); + + b.HasKey("GroupsId", "RightsId"); + + b.HasIndex("RightsId"); + + b.ToTable("GroupEntityRightEntity"); + }); + + modelBuilder.Entity("GroupEntityUserEntity", b => + { + b.Property("GroupsId") + .HasColumnType("TEXT"); + + b.Property("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 + } + } +} diff --git a/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.cs b/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.cs new file mode 100644 index 0000000..19dd078 --- /dev/null +++ b/ApplicationHub.Data.EF/Migrations/20250809103237_InitDB.cs @@ -0,0 +1,158 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ApplicationHub.Data.EF.Migrations +{ + /// + public partial class InitDB : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Groups", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(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(type: "TEXT", nullable: false), + Name = table.Column(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(type: "TEXT", nullable: false), + UserName = table.Column(type: "TEXT", maxLength: 255, nullable: false), + Email = table.Column(type: "TEXT", maxLength: 255, nullable: false), + PasswordHash = table.Column(type: "TEXT", maxLength: 255, nullable: true), + Active = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GroupEntityRightEntity", + columns: table => new + { + GroupsId = table.Column(type: "TEXT", nullable: false), + RightsId = table.Column(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(type: "TEXT", nullable: false), + UsersId = table.Column(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]); + } + + /// + 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"); + } + } +} diff --git a/ApplicationHub.Data.EF/Migrations/AuthenticationDataContextModelSnapshot.cs b/ApplicationHub.Data.EF/Migrations/AuthenticationDataContextModelSnapshot.cs new file mode 100644 index 0000000..8a67a53 --- /dev/null +++ b/ApplicationHub.Data.EF/Migrations/AuthenticationDataContextModelSnapshot.cs @@ -0,0 +1,142 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.RightEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Rights"); + }); + + modelBuilder.Entity("ApplicationHub.Data.EF.Authentication.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("GroupEntityRightEntity", b => + { + b.Property("GroupsId") + .HasColumnType("TEXT"); + + b.Property("RightsId") + .HasColumnType("TEXT"); + + b.HasKey("GroupsId", "RightsId"); + + b.HasIndex("RightsId"); + + b.ToTable("GroupEntityRightEntity"); + }); + + modelBuilder.Entity("GroupEntityUserEntity", b => + { + b.Property("GroupsId") + .HasColumnType("TEXT"); + + b.Property("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 + } + } +} diff --git a/ApplicationHub.Data.InMemory/ApplicationHub.Data.InMemory.csproj b/ApplicationHub.Data.InMemory/ApplicationHub.Data.InMemory.csproj new file mode 100644 index 0000000..dec9bcf --- /dev/null +++ b/ApplicationHub.Data.InMemory/ApplicationHub.Data.InMemory.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/ApplicationHub.Data.InMemory/Authentication/Entities/GroupDto.cs b/ApplicationHub.Data.InMemory/Authentication/Entities/GroupDto.cs new file mode 100644 index 0000000..1995bc3 --- /dev/null +++ b/ApplicationHub.Data.InMemory/Authentication/Entities/GroupDto.cs @@ -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 Rights { get; set; } = new(); +} \ No newline at end of file diff --git a/ApplicationHub.Data.InMemory/Authentication/Entities/RightDto.cs b/ApplicationHub.Data.InMemory/Authentication/Entities/RightDto.cs new file mode 100644 index 0000000..6d3f6b3 --- /dev/null +++ b/ApplicationHub.Data.InMemory/Authentication/Entities/RightDto.cs @@ -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; } = ""; +} \ No newline at end of file diff --git a/ApplicationHub.Data.InMemory/Authentication/Entities/UserDto.cs b/ApplicationHub.Data.InMemory/Authentication/Entities/UserDto.cs new file mode 100644 index 0000000..adc7832 --- /dev/null +++ b/ApplicationHub.Data.InMemory/Authentication/Entities/UserDto.cs @@ -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 Groups { get; set; } = new(); +} \ No newline at end of file diff --git a/ApplicationHub.Data.InMemory/Authentication/Sources.cs b/ApplicationHub.Data.InMemory/Authentication/Sources.cs new file mode 100644 index 0000000..e92352f --- /dev/null +++ b/ApplicationHub.Data.InMemory/Authentication/Sources.cs @@ -0,0 +1,76 @@ +using ApplicationHub.Data.InMemory.Authentication.Entities; + +namespace ApplicationHub.Data.InMemory.Authentication; + +internal static class RightData +{ + public static List 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 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 + { + 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 + { + RightData.Source.First(x => x.Name == "READ"), + } + } + }; +} + +internal static class UserData +{ + public static List 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 + { + GroupData.Source.First(x => x.Name == "Group_Name_Administrator"), + } + }, + }; +} \ No newline at end of file diff --git a/ApplicationHub.Data.InMemory/Authentication/UserDataContext.cs b/ApplicationHub.Data.InMemory/Authentication/UserDataContext.cs new file mode 100644 index 0000000..53fab88 --- /dev/null +++ b/ApplicationHub.Data.InMemory/Authentication/UserDataContext.cs @@ -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>(result).FirstOrDefault(); + } +} + diff --git a/ApplicationHub.Data.InMemory/Configuration/MappingProfile.cs b/ApplicationHub.Data.InMemory/Configuration/MappingProfile.cs new file mode 100644 index 0000000..587767f --- /dev/null +++ b/ApplicationHub.Data.InMemory/Configuration/MappingProfile.cs @@ -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(); + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/ApplicationHub.Domain.Contracts/ApplicationHub.Domain.Contracts.csproj b/ApplicationHub.Domain.Contracts/ApplicationHub.Domain.Contracts.csproj new file mode 100644 index 0000000..17b910f --- /dev/null +++ b/ApplicationHub.Domain.Contracts/ApplicationHub.Domain.Contracts.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/ApplicationHub.Domain.Contracts/Authentication/IUserRepository.cs b/ApplicationHub.Domain.Contracts/Authentication/IUserRepository.cs new file mode 100644 index 0000000..2a2e3dc --- /dev/null +++ b/ApplicationHub.Domain.Contracts/Authentication/IUserRepository.cs @@ -0,0 +1,8 @@ +using ApplicationHub.Domain.Contracts.Authentication.Models; + +namespace ApplicationHub.Domain.Contracts.Authentication; + +public interface IUserRepository +{ + public User? GetUserByUserName(string userName); +} \ No newline at end of file diff --git a/ApplicationHub.Domain.Contracts/Authentication/Models/Group.cs b/ApplicationHub.Domain.Contracts/Authentication/Models/Group.cs new file mode 100644 index 0000000..b721349 --- /dev/null +++ b/ApplicationHub.Domain.Contracts/Authentication/Models/Group.cs @@ -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 Rights { get; set; } = new List(); +} \ No newline at end of file diff --git a/ApplicationHub.Domain.Contracts/Authentication/Models/Right.cs b/ApplicationHub.Domain.Contracts/Authentication/Models/Right.cs new file mode 100644 index 0000000..73c9913 --- /dev/null +++ b/ApplicationHub.Domain.Contracts/Authentication/Models/Right.cs @@ -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; } = ""; +} \ No newline at end of file diff --git a/ApplicationHub.Domain.Contracts/Authentication/Models/User.cs b/ApplicationHub.Domain.Contracts/Authentication/Models/User.cs new file mode 100644 index 0000000..e17454a --- /dev/null +++ b/ApplicationHub.Domain.Contracts/Authentication/Models/User.cs @@ -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 Groups { get; set; } = new List(); +} \ No newline at end of file diff --git a/ApplicationHub.Domain/ApplicationHub.Domain.csproj b/ApplicationHub.Domain/ApplicationHub.Domain.csproj new file mode 100644 index 0000000..6886c15 --- /dev/null +++ b/ApplicationHub.Domain/ApplicationHub.Domain.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + Adapter + + + + + + + diff --git a/ApplicationHub.Domain/Authentication/Services/AuthenticationService.cs b/ApplicationHub.Domain/Authentication/Services/AuthenticationService.cs new file mode 100644 index 0000000..7fa04fd --- /dev/null +++ b/ApplicationHub.Domain/Authentication/Services/AuthenticationService.cs @@ -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); + } +} \ No newline at end of file diff --git a/ApplicationHub.sln b/ApplicationHub.sln new file mode 100644 index 0000000..c1d69c7 --- /dev/null +++ b/ApplicationHub.sln @@ -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 diff --git a/ApplicationHub/ApplicationHub.csproj b/ApplicationHub/ApplicationHub.csproj new file mode 100644 index 0000000..3f750c6 --- /dev/null +++ b/ApplicationHub/ApplicationHub.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/ApplicationHub/Configuration/AdapterConfiguration.cs b/ApplicationHub/Configuration/AdapterConfiguration.cs new file mode 100644 index 0000000..f0129f6 --- /dev/null +++ b/ApplicationHub/Configuration/AdapterConfiguration.cs @@ -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 +{ + /// + /// Setup the used Adapter for this Application + /// + /// the Web Application Builder from ASP .NET + public static void Configure(WebApplicationBuilder builder) + { + // use EF implementation + builder.Services.AddAutoMapper(typeof(UserEntity)); + builder.Services.AddScoped(); + + // use InMemory implementation + // builder.Services.AddScoped(); + // builder.Services.AddAutoMapper(typeof(UserDto)); + } +} \ No newline at end of file diff --git a/ApplicationHub/Configuration/AuthenticationConfiguration.cs b/ApplicationHub/Configuration/AuthenticationConfiguration.cs new file mode 100644 index 0000000..6a444d6 --- /dev/null +++ b/ApplicationHub/Configuration/AuthenticationConfiguration.cs @@ -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 +{ + /// + /// Enable the JWT Authentication for the ASP .NET Service + /// + /// the Web Application Builder from ASP .NET + 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, PasswordHasher>(); + } +} \ No newline at end of file diff --git a/ApplicationHub/Configuration/ConfigFileConfiguration.cs b/ApplicationHub/Configuration/ConfigFileConfiguration.cs new file mode 100644 index 0000000..90d4124 --- /dev/null +++ b/ApplicationHub/Configuration/ConfigFileConfiguration.cs @@ -0,0 +1,15 @@ +using ApplicationHub.Models; + +namespace ApplicationHub.Configuration; + +public static class ConfigFileConfiguration +{ + /// + /// Add some Configuration Values to the Dependency Injection + /// + /// the Web Application Builder from ASP .NET + public static void Configure(WebApplicationBuilder builder) + { + builder.Services.Configure(builder.Configuration.GetSection("JwtSettings")); + } +} \ No newline at end of file diff --git a/ApplicationHub/Configuration/DataContextConfiguration.cs b/ApplicationHub/Configuration/DataContextConfiguration.cs new file mode 100644 index 0000000..c888688 --- /dev/null +++ b/ApplicationHub/Configuration/DataContextConfiguration.cs @@ -0,0 +1,28 @@ +using ApplicationHub.Data.EF.Authentication; +using Microsoft.EntityFrameworkCore; + +namespace ApplicationHub.Configuration; + +public static class DataContextConfiguration +{ + /// + /// Set the DataContexts for Dependency Injection + /// + /// the Web Application Builder from ASP .NET + public static void Configure(WebApplicationBuilder builder) + { + builder.Services.AddDbContext(o => o.UseSqlite(builder.Configuration.GetConnectionString("AuthenticationConnection"))); + } + + /// + /// Runs the DB Migrations

+ /// Note: please run this after WebApplicationBuilder was build and before the Run of the WebApplication was called! + ///
+ /// the builded WebApplication from ASP .NET + public static void Migrate(WebApplication app) + { + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); + } +} \ No newline at end of file diff --git a/ApplicationHub/Configuration/ServiceConfiguration.cs b/ApplicationHub/Configuration/ServiceConfiguration.cs new file mode 100644 index 0000000..bba3214 --- /dev/null +++ b/ApplicationHub/Configuration/ServiceConfiguration.cs @@ -0,0 +1,15 @@ +using Adapter.Authentication.Services; + +namespace ApplicationHub.Configuration; + +public static class ServiceConfiguration +{ + /// + /// Add the Services from Domain the the Dependency Injection + /// + /// the Web Application Builder from ASP .NET + public static void Configure(WebApplicationBuilder builder) + { + builder.Services.AddScoped(); + } +} \ No newline at end of file diff --git a/ApplicationHub/Controllers/Authentication/LoginController.cs b/ApplicationHub/Controllers/Authentication/LoginController.cs new file mode 100644 index 0000000..cb8fc33 --- /dev/null +++ b/ApplicationHub/Controllers/Authentication/LoginController.cs @@ -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, IPasswordHasher 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 ?? ""}"); + } + + 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); + } +} \ No newline at end of file diff --git a/ApplicationHub/Controllers/Message/GreeterController.cs b/ApplicationHub/Controllers/Message/GreeterController.cs new file mode 100644 index 0000000..469cbef --- /dev/null +++ b/ApplicationHub/Controllers/Message/GreeterController.cs @@ -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 ""; + } +} \ No newline at end of file diff --git a/ApplicationHub/Models/JwtSettings.cs b/ApplicationHub/Models/JwtSettings.cs new file mode 100644 index 0000000..2a12d33 --- /dev/null +++ b/ApplicationHub/Models/JwtSettings.cs @@ -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; } = ""; +} \ No newline at end of file diff --git a/ApplicationHub/Models/UserLogin.cs b/ApplicationHub/Models/UserLogin.cs new file mode 100644 index 0000000..977b57b --- /dev/null +++ b/ApplicationHub/Models/UserLogin.cs @@ -0,0 +1,7 @@ +namespace ApplicationHub.Models; + +public class UserLogin +{ + public string UserName { get; set; } = ""; + public string Password { get; set; } = ""; +} \ No newline at end of file diff --git a/ApplicationHub/Program.cs b/ApplicationHub/Program.cs new file mode 100644 index 0000000..c6333ec --- /dev/null +++ b/ApplicationHub/Program.cs @@ -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(); \ No newline at end of file diff --git a/ApplicationHub/Properties/launchSettings.json b/ApplicationHub/Properties/launchSettings.json new file mode 100644 index 0000000..24f1e5e --- /dev/null +++ b/ApplicationHub/Properties/launchSettings.json @@ -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" + } + } + } +} diff --git a/ApplicationHub/appsettings.Development.json b/ApplicationHub/appsettings.Development.json new file mode 100644 index 0000000..4a52192 --- /dev/null +++ b/ApplicationHub/appsettings.Development.json @@ -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" + } +} diff --git a/ApplicationHub/appsettings.json b/ApplicationHub/appsettings.json new file mode 100644 index 0000000..aae2109 --- /dev/null +++ b/ApplicationHub/appsettings.json @@ -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" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29