diff --git a/Database/Dockerfile b/Database/Dockerfile
index a65c2ef..fea6d9d 100644
--- a/Database/Dockerfile
+++ b/Database/Dockerfile
@@ -1,6 +1,6 @@
FROM postgres:16.1-alpine AS build
-ADD ./init-database.sql /docker-entrypoint-initdb.d
-RUN chmod 644 /docker-entrypoint-initdb.d/init-database.sql
+ADD ./*.sql /docker-entrypoint-initdb.d
+RUN chmod 644 /docker-entrypoint-initdb.d/*.sql
EXPOSE 5432
ENTRYPOINT [ "docker-entrypoint.sh" ]
CMD [ "postgres" ]
\ No newline at end of file
diff --git a/Database/init-identity-database.sql b/Database/init-identity-database.sql
new file mode 100644
index 0000000..9c4daa6
--- /dev/null
+++ b/Database/init-identity-database.sql
@@ -0,0 +1,97 @@
+CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
+ "MigrationId" character varying(150) NOT NULL,
+ "ProductVersion" character varying(32) NOT NULL,
+ CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId")
+);
+
+START TRANSACTION;
+
+CREATE TABLE "AspNetRoles" (
+ "Id" text NOT NULL,
+ "Name" character varying(256),
+ "NormalizedName" character varying(256),
+ "ConcurrencyStamp" text,
+ CONSTRAINT "PK_AspNetRoles" PRIMARY KEY ("Id")
+);
+
+CREATE TABLE "AspNetUsers" (
+ "Id" text NOT NULL,
+ "UserName" character varying(256),
+ "NormalizedUserName" character varying(256),
+ "Email" character varying(256),
+ "NormalizedEmail" character varying(256),
+ "EmailConfirmed" boolean NOT NULL,
+ "PasswordHash" text,
+ "SecurityStamp" text,
+ "ConcurrencyStamp" text,
+ "PhoneNumber" text,
+ "PhoneNumberConfirmed" boolean NOT NULL,
+ "TwoFactorEnabled" boolean NOT NULL,
+ "LockoutEnd" timestamp with time zone,
+ "LockoutEnabled" boolean NOT NULL,
+ "AccessFailedCount" integer NOT NULL,
+ CONSTRAINT "PK_AspNetUsers" PRIMARY KEY ("Id")
+);
+
+CREATE TABLE "AspNetRoleClaims" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "RoleId" text NOT NULL,
+ "ClaimType" text,
+ "ClaimValue" text,
+ CONSTRAINT "PK_AspNetRoleClaims" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_AspNetRoleClaims_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE
+);
+
+CREATE TABLE "AspNetUserClaims" (
+ "Id" integer GENERATED BY DEFAULT AS IDENTITY,
+ "UserId" text NOT NULL,
+ "ClaimType" text,
+ "ClaimValue" text,
+ CONSTRAINT "PK_AspNetUserClaims" PRIMARY KEY ("Id"),
+ CONSTRAINT "FK_AspNetUserClaims_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
+);
+
+CREATE TABLE "AspNetUserLogins" (
+ "LoginProvider" text NOT NULL,
+ "ProviderKey" text NOT NULL,
+ "ProviderDisplayName" text,
+ "UserId" text NOT NULL,
+ CONSTRAINT "PK_AspNetUserLogins" PRIMARY KEY ("LoginProvider", "ProviderKey"),
+ CONSTRAINT "FK_AspNetUserLogins_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
+);
+
+CREATE TABLE "AspNetUserRoles" (
+ "UserId" text NOT NULL,
+ "RoleId" text NOT NULL,
+ CONSTRAINT "PK_AspNetUserRoles" PRIMARY KEY ("UserId", "RoleId"),
+ CONSTRAINT "FK_AspNetUserRoles_AspNetRoles_RoleId" FOREIGN KEY ("RoleId") REFERENCES "AspNetRoles" ("Id") ON DELETE CASCADE,
+ CONSTRAINT "FK_AspNetUserRoles_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
+);
+
+CREATE TABLE "AspNetUserTokens" (
+ "UserId" text NOT NULL,
+ "LoginProvider" text NOT NULL,
+ "Name" text NOT NULL,
+ "Value" text,
+ CONSTRAINT "PK_AspNetUserTokens" PRIMARY KEY ("UserId", "LoginProvider", "Name"),
+ CONSTRAINT "FK_AspNetUserTokens_AspNetUsers_UserId" FOREIGN KEY ("UserId") REFERENCES "AspNetUsers" ("Id") ON DELETE CASCADE
+);
+
+CREATE INDEX "IX_AspNetRoleClaims_RoleId" ON "AspNetRoleClaims" ("RoleId");
+
+CREATE UNIQUE INDEX "RoleNameIndex" ON "AspNetRoles" ("NormalizedName");
+
+CREATE INDEX "IX_AspNetUserClaims_UserId" ON "AspNetUserClaims" ("UserId");
+
+CREATE INDEX "IX_AspNetUserLogins_UserId" ON "AspNetUserLogins" ("UserId");
+
+CREATE INDEX "IX_AspNetUserRoles_RoleId" ON "AspNetUserRoles" ("RoleId");
+
+CREATE INDEX "EmailIndex" ON "AspNetUsers" ("NormalizedEmail");
+
+CREATE UNIQUE INDEX "UserNameIndex" ON "AspNetUsers" ("NormalizedUserName");
+
+INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
+VALUES ('20240206125053_Init', '8.0.1');
+
+COMMIT;
\ No newline at end of file
diff --git a/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj b/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj
index 5534bfe..cc4ff74 100644
--- a/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj
+++ b/RhSolutions.Api.Tests/RhSolutions.Api.Tests.csproj
@@ -12,7 +12,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/RhSolutions.Api/ConnectionStringsUtil.cs b/RhSolutions.Api/ConnectionStringsUtil.cs
new file mode 100644
index 0000000..b27eb4c
--- /dev/null
+++ b/RhSolutions.Api/ConnectionStringsUtil.cs
@@ -0,0 +1,30 @@
+public class ConnectionStringsUtil
+{
+ private string dbHost;
+ private string dbPort;
+ private string dbName;
+ private string dbUser;
+ private string dbPassword;
+ private IConfiguration _configuration;
+
+ public ConnectionStringsUtil(IConfiguration configuration)
+ {
+ _configuration = configuration;
+ dbHost = configuration["DB_HOST"] ?? "localhost";
+ dbPort = configuration["DB_PORT"] ?? "5000";
+ dbName = configuration["DB_DATABASE"] ?? "rhsolutions";
+ dbUser = configuration["DB_USER"] ?? "chebser";
+ dbPassword = configuration["DB_PASSWORD"] ?? "Rehau-987";
+ }
+ public string GetRhDbString()
+ {
+ return _configuration["ConnectionsStrings:RhSolutionsLocal"]
+ ?? $"Host={dbHost};Port={dbPort};Database={dbName};Username={dbUser};Password={dbPassword}";
+ }
+
+ public string GetIdentityDbString()
+ {
+ return _configuration["ConnectionsStrings:RhSolutionsLocal"]
+ ?? $"Host={dbHost};Port={dbPort};Database=Identity;Username={dbUser};Password={dbPassword}";
+ }
+}
diff --git a/RhSolutions.Api/Controllers/AccountController.cs b/RhSolutions.Api/Controllers/AccountController.cs
new file mode 100644
index 0000000..647a6bc
--- /dev/null
+++ b/RhSolutions.Api/Controllers/AccountController.cs
@@ -0,0 +1,72 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Text;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
+using System.Security.Claims;
+
+namespace RhSolutions.Api.Controllers;
+
+[ApiController]
+[Route("/api/account")]
+public class AccountController : ControllerBase
+{
+ private SignInManager _signInManager;
+ private UserManager _userManager;
+ private IConfiguration _configuration;
+ public AccountController(SignInManager signInManager,
+ UserManager userManager,
+ IConfiguration configuration)
+ {
+ _signInManager = signInManager;
+ _userManager = userManager;
+ _configuration = configuration;
+ }
+
+ ///
+ /// Получение токена
+ ///
+ ///
+ ///
+ [HttpPost("token")]
+ public async Task Token([FromBody] Credentials credentials)
+ {
+ if (await CheckPassword(credentials))
+ {
+ JwtSecurityTokenHandler handler = new();
+ string jwtSecret = _configuration["JWT_SECRET"] ?? "mold-smartness-arrive-overstate-aspirin";
+ byte[] secret = Encoding.ASCII.GetBytes(jwtSecret);
+ SecurityTokenDescriptor descriptor = new()
+ {
+ Subject = new ClaimsIdentity(new Claim[]
+ {
+ new (ClaimTypes.Name, credentials.Username)
+ }),
+ Expires = DateTime.UtcNow.AddDays(1),
+ SigningCredentials = new(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256Signature)
+ };
+ SecurityToken token = handler.CreateToken(descriptor);
+ return Ok(new
+ {
+ Success = true,
+ Token = handler.WriteToken(token)
+ });
+ }
+ return Unauthorized();
+ }
+
+ private async Task CheckPassword(Credentials credentials)
+ {
+ IdentityUser? user = await _userManager.FindByNameAsync(credentials.Username);
+ if (user != null)
+ {
+ return (await _signInManager.CheckPasswordSignInAsync(user, credentials.Password, true)).Succeeded;
+ }
+ return false;
+ }
+ public class Credentials
+ {
+ public string Username { get; set; } = string.Empty;
+ public string Password { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/RhSolutions.Api/Controllers/ProductsController.cs b/RhSolutions.Api/Controllers/ProductsController.cs
index 99c0af0..b2aa43e 100644
--- a/RhSolutions.Api/Controllers/ProductsController.cs
+++ b/RhSolutions.Api/Controllers/ProductsController.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using RhSolutions.Models;
using RhSolutions.Api.Services;
-using System.Linq;
+using Microsoft.AspNetCore.Authorization;
namespace RhSolutions.Api.Controllers
{
@@ -45,6 +45,7 @@ namespace RhSolutions.Api.Controllers
///
///
[HttpPost]
+ [Authorize(AuthenticationSchemes = "Identity.Application, Bearer")]
public IActionResult PostProductsFromXls()
{
try
@@ -80,6 +81,7 @@ namespace RhSolutions.Api.Controllers
///
///
[HttpDelete]
+ [Authorize(AuthenticationSchemes = "Identity.Application, Bearer")]
public IActionResult DeleteAllProducts()
{
List deleted = new();
diff --git a/RhSolutions.Api/Migrations/Identity/20240206125053_Init.Designer.cs b/RhSolutions.Api/Migrations/Identity/20240206125053_Init.Designer.cs
new file mode 100644
index 0000000..9e2e3e8
--- /dev/null
+++ b/RhSolutions.Api/Migrations/Identity/20240206125053_Init.Designer.cs
@@ -0,0 +1,277 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using RhSolutions.Models;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations.Identity
+{
+ [DbContext(typeof(IdentityContext))]
+ [Migration("20240206125053_Init")]
+ partial class Init
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property("ProviderKey")
+ .HasColumnType("text");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/RhSolutions.Api/Migrations/Identity/20240206125053_Init.cs b/RhSolutions.Api/Migrations/Identity/20240206125053_Init.cs
new file mode 100644
index 0000000..c4f1052
--- /dev/null
+++ b/RhSolutions.Api/Migrations/Identity/20240206125053_Init.cs
@@ -0,0 +1,223 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations.Identity
+{
+ ///
+ public partial class Init : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(type: "boolean", nullable: false),
+ PasswordHash = table.Column(type: "text", nullable: true),
+ SecurityStamp = table.Column(type: "text", nullable: true),
+ ConcurrencyStamp = table.Column(type: "text", nullable: true),
+ PhoneNumber = table.Column(type: "text", nullable: true),
+ PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false),
+ TwoFactorEnabled = table.Column(type: "boolean", nullable: false),
+ LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true),
+ LockoutEnabled = table.Column(type: "boolean", nullable: false),
+ AccessFailedCount = table.Column(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ RoleId = table.Column(type: "text", nullable: false),
+ ClaimType = table.Column(type: "text", nullable: true),
+ ClaimValue = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ UserId = table.Column(type: "text", nullable: false),
+ ClaimType = table.Column(type: "text", nullable: true),
+ ClaimValue = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(type: "text", nullable: false),
+ ProviderKey = table.Column(type: "text", nullable: false),
+ ProviderDisplayName = table.Column(type: "text", nullable: true),
+ UserId = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(type: "text", nullable: false),
+ RoleId = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(type: "text", nullable: false),
+ LoginProvider = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ Value = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUsers");
+ }
+ }
+}
diff --git a/RhSolutions.Api/Migrations/Identity/IdentityContextModelSnapshot.cs b/RhSolutions.Api/Migrations/Identity/IdentityContextModelSnapshot.cs
new file mode 100644
index 0000000..b20f179
--- /dev/null
+++ b/RhSolutions.Api/Migrations/Identity/IdentityContextModelSnapshot.cs
@@ -0,0 +1,274 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using RhSolutions.Models;
+
+#nullable disable
+
+namespace RhSolutions.Api.Migrations.Identity
+{
+ [DbContext(typeof(IdentityContext))]
+ partial class IdentityContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("integer");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("text");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("boolean");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("text");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property("ProviderKey")
+ .HasColumnType("text");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("RoleId")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("text");
+
+ b.Property("LoginProvider")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/RhSolutions.Api/Models/IdentityContext.cs b/RhSolutions.Api/Models/IdentityContext.cs
new file mode 100644
index 0000000..fbac8c9
--- /dev/null
+++ b/RhSolutions.Api/Models/IdentityContext.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+
+namespace RhSolutions.Models;
+public class IdentityContext : IdentityDbContext
+{
+ public IdentityContext(DbContextOptions options) : base(options) { }
+}
\ No newline at end of file
diff --git a/RhSolutions.Api/Models/IdentitySeedData.cs b/RhSolutions.Api/Models/IdentitySeedData.cs
new file mode 100644
index 0000000..7cd2ff8
--- /dev/null
+++ b/RhSolutions.Api/Models/IdentitySeedData.cs
@@ -0,0 +1,39 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace RhSolutions.Models;
+
+public class IdentitySeedData
+{
+ public static void CreateAdminAccount(IServiceProvider serviceProvider, IConfiguration configuration)
+ {
+ CreateAdminAccountAsync(serviceProvider, configuration).Wait();
+ }
+
+ public static async Task CreateAdminAccountAsync(IServiceProvider serviceProvider, IConfiguration configuration)
+ {
+ serviceProvider = serviceProvider.CreateScope().ServiceProvider;
+ UserManager userManager = serviceProvider.GetRequiredService>();
+ RoleManager roleManager = serviceProvider.GetRequiredService>();
+
+ string username = "admin";
+ string password = configuration["ADMIN_PASSWORD"] ?? "RhSolutionsPassword123!";
+ string role = "Admins";
+
+ if (await userManager.FindByNameAsync(username) == null)
+ {
+ if (await roleManager.FindByNameAsync(role) == null)
+ {
+ await roleManager.CreateAsync(new(role));
+ }
+ IdentityUser user = new()
+ {
+ UserName = username
+ };
+ IdentityResult result = await userManager.CreateAsync(user, password);
+ if (result.Succeeded)
+ {
+ await userManager.AddToRoleAsync(user, role);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RhSolutions.Api/Program.cs b/RhSolutions.Api/Program.cs
index 11f6722..ac5269c 100644
--- a/RhSolutions.Api/Program.cs
+++ b/RhSolutions.Api/Program.cs
@@ -5,26 +5,31 @@ using RhSolutions.Api.Middleware;
using RhSolutions.MLModifiers;
using Microsoft.OpenApi.Models;
using System.Reflection;
+using Microsoft.AspNetCore.Identity;
+using System.Text;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
-
-string dbHost = builder.Configuration["DB_HOST"] ?? "localhost",
- dbPort = builder.Configuration["DB_PORT"] ?? "5000",
- dbName = builder.Configuration["DB_DATABASE"] ?? "rhsolutions",
- dbUser = builder.Configuration["DB_USER"] ?? "chebser",
- dbPassword = builder.Configuration["DB_PASSWORD"] ?? "Rehau-987";
-
-string connectionString = builder.Configuration["ConnectionsStrings:RhSolutionsLocal"]
- ?? $"Host={dbHost};Port={dbPort};Database={dbName};Username={dbUser};Password={dbPassword}";
+ConnectionStringsUtil connectionStringsUtil = new(builder.Configuration);
builder.Services.AddDbContext(opts =>
{
- opts.UseNpgsql(connectionString);
+ opts.UseNpgsql(connectionStringsUtil.GetRhDbString());
if (builder.Environment.IsDevelopment())
{
opts.EnableSensitiveDataLogging(true);
}
});
+builder.Services.AddDbContext(opts =>
+{
+ opts.UseNpgsql(connectionStringsUtil.GetIdentityDbString());
+});
+
+builder.Services.AddIdentity()
+ .AddEntityFrameworkStores();
+
builder.Services.AddScoped()
.AddScoped();
builder.Services.AddModifiers();
@@ -43,10 +48,38 @@ builder.Services.AddSwaggerGen(options =>
Email = @"serghei@cebotari.ru"
}
- });
+ });
+
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
+builder.Services.AddAuthentication()
+ .AddJwtBearer(opts =>
+ {
+ opts.RequireHttpsMetadata = false;
+ opts.SaveToken = true;
+ opts.TokenValidationParameters = new()
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.ASCII.GetBytes(builder.Configuration["JWT_SECRET"] ?? "mold-smartness-arrive-overstate-aspirin")),
+ ValidateAudience = false,
+ ValidateIssuer = false
+ };
+ opts.Events = new JwtBearerEvents()
+ {
+ OnTokenValidated = async context =>
+ {
+ var userManager = context.HttpContext.RequestServices
+ .GetRequiredService>();
+ var signInManager = context.HttpContext.RequestServices
+ .GetRequiredService>();
+ string username = context.Principal!.FindFirst(ClaimTypes.Name)!.Value;
+ IdentityUser? idUser = await userManager.FindByNameAsync(username);
+ context.Principal = await signInManager.CreateUserPrincipalAsync(idUser!);
+ }
+ };
+ });
var app = builder.Build();
@@ -57,5 +90,8 @@ app.UseSwagger().UseSwaggerUI(options =>
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.RoutePrefix = string.Empty;
});
+app.UseAuthentication();
+app.UseAuthorization();
+IdentitySeedData.CreateAdminAccount(app.Services, app.Configuration);
app.Run();
diff --git a/RhSolutions.Api/RhSolutions.Api.csproj b/RhSolutions.Api/RhSolutions.Api.csproj
index eb3916a..7690469 100644
--- a/RhSolutions.Api/RhSolutions.Api.csproj
+++ b/RhSolutions.Api/RhSolutions.Api.csproj
@@ -10,18 +10,21 @@
-
-
-
-
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 1c01b58..2e19f56 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,6 +11,7 @@ services:
- DB_DATABASE=rhsolutions
- DB_USER=chebser
- DB_PASSWORD=Rehau-987
+ - ASPNETCORE_ENVIRONMENT=Development
networks:
- rhsolutions
volumes: