From 06321840a83916fdce12f89c8dd19b5e6bd66ba8 Mon Sep 17 00:00:00 2001 From: Gianmarco Date: Mon, 1 Dec 2025 15:15:05 +0100 Subject: [PATCH] Membership --- StandManager.Domain/Entita/ApplicationUser.cs | 8 + StandManager.Domain/Entita/Utente.cs | 2 +- .../StandManager.Domain.csproj | 6 +- .../DAL/Context/StandManagerDbContext.cs | 3 +- .../20251201110046_Initial.Designer.cs | 147 ++++++++++ .../Migrations/20251201110046_Initial.cs | 91 +++++++ .../20251201115553_UserApp.Designer.cs | 253 ++++++++++++++++++ .../Migrations/20251201115553_UserApp.cs | 224 ++++++++++++++++ .../20251201141435_removedUser.Designer.cs | 95 +++++++ .../Migrations/20251201141435_removedUser.cs | 139 ++++++++++ .../StandManagerDbContextModelSnapshot.cs | 92 +++++++ .../StandManager.Infrastructure.csproj | 8 +- .../Interfaces/IManagerService.cs | 11 +- .../Interfaces/IMembershipService.cs | 15 ++ .../Interfaces/IUtenteService.cs | 2 +- StandManager.Service/ManagerService.cs | 2 +- .../StandManagerGenericRepository.cs | 1 + StandManager.slnx | 6 +- .../Account/IdentityRedirectManager.cs | 59 ++++ ...RevalidatingAuthenticationStateProvider.cs | 46 ++++ .../Account/IdentityUserAccessor.cs | 20 ++ StandManager/Components/App.razor | 14 +- .../Components/Layout/MainLayout.razor | 119 ++++---- .../Components/Pages/Account/Login.razor | 90 +++++++ .../Components/Pages/Account/Logout.razor | 15 ++ StandManager/Components/Pages/Home.razor | 3 + .../Pages/Management/Dashboard.razor | 13 +- .../Components/Pages/Management/Login.razor | 66 ++--- StandManager/LoginUtils.cs | 40 +++ StandManager/Program.cs | 91 +++++-- StandManager/StandManager.csproj | 4 +- StandManager/appsettings.json | 2 +- 32 files changed, 1546 insertions(+), 141 deletions(-) create mode 100644 StandManager.Domain/Entita/ApplicationUser.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201110046_Initial.Designer.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201110046_Initial.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201115553_UserApp.Designer.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201115553_UserApp.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201141435_removedUser.Designer.cs create mode 100644 StandManager.Infrastructure/Migrations/20251201141435_removedUser.cs create mode 100644 StandManager.Infrastructure/Migrations/StandManagerDbContextModelSnapshot.cs create mode 100644 StandManager.Service/Interfaces/IMembershipService.cs create mode 100644 StandManager/Components/Account/IdentityRedirectManager.cs create mode 100644 StandManager/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs create mode 100644 StandManager/Components/Account/IdentityUserAccessor.cs create mode 100644 StandManager/Components/Pages/Account/Login.razor create mode 100644 StandManager/Components/Pages/Account/Logout.razor create mode 100644 StandManager/LoginUtils.cs diff --git a/StandManager.Domain/Entita/ApplicationUser.cs b/StandManager.Domain/Entita/ApplicationUser.cs new file mode 100644 index 0000000..33eb17e --- /dev/null +++ b/StandManager.Domain/Entita/ApplicationUser.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity; + +namespace StandManager.Domain.Entita; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser +{ +} \ No newline at end of file diff --git a/StandManager.Domain/Entita/Utente.cs b/StandManager.Domain/Entita/Utente.cs index e7a0a6a..9654ae8 100644 --- a/StandManager.Domain/Entita/Utente.cs +++ b/StandManager.Domain/Entita/Utente.cs @@ -13,4 +13,4 @@ public class Utente : EntitaBase { return $"{Nome} {Cognome}"; } -} \ No newline at end of file +} diff --git a/StandManager.Domain/StandManager.Domain.csproj b/StandManager.Domain/StandManager.Domain.csproj index 8abd2bb..2685bfa 100644 --- a/StandManager.Domain/StandManager.Domain.csproj +++ b/StandManager.Domain/StandManager.Domain.csproj @@ -8,11 +8,13 @@ - + - + + + diff --git a/StandManager.Infrastructure/DAL/Context/StandManagerDbContext.cs b/StandManager.Infrastructure/DAL/Context/StandManagerDbContext.cs index dc2fe8a..1133bac 100644 --- a/StandManager.Infrastructure/DAL/Context/StandManagerDbContext.cs +++ b/StandManager.Infrastructure/DAL/Context/StandManagerDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using OAService.Infrastructure.DAL.Context; using StandManager.Domain.Entita; diff --git a/StandManager.Infrastructure/Migrations/20251201110046_Initial.Designer.cs b/StandManager.Infrastructure/Migrations/20251201110046_Initial.Designer.cs new file mode 100644 index 0000000..ff33446 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201110046_Initial.Designer.cs @@ -0,0 +1,147 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using StandManager.Infrastructure.DAL.Context; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + [DbContext(typeof(StandManagerDbContext))] + [Migration("20251201110046_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("StandManager.Domain.Entita.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ApplicationUsers"); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Cognome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataCreazione") + .HasColumnType("datetime2"); + + b.Property("DataModifica") + .HasColumnType("datetime2"); + + b.Property("Eliminato") + .HasColumnType("bit"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IdUtenteCreazione") + .HasColumnType("uniqueidentifier"); + + b.Property("IdUtenteModifica") + .HasColumnType("uniqueidentifier"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IdUtenteCreazione"); + + b.HasIndex("IdUtenteModifica"); + + b.ToTable("Utente"); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteCreazione") + .WithMany() + .HasForeignKey("IdUtenteCreazione"); + + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteModifica") + .WithMany() + .HasForeignKey("IdUtenteModifica"); + + b.Navigation("UtenteCreazione"); + + b.Navigation("UtenteModifica"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/StandManager.Infrastructure/Migrations/20251201110046_Initial.cs b/StandManager.Infrastructure/Migrations/20251201110046_Initial.cs new file mode 100644 index 0000000..4d256d0 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201110046_Initial.cs @@ -0,0 +1,91 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(max)", nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(max)", nullable: true), + Email = table.Column(type: "nvarchar(max)", nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(max)", nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Utente", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Username = table.Column(type: "nvarchar(max)", nullable: false), + Email = table.Column(type: "nvarchar(max)", nullable: false), + Password = table.Column(type: "nvarchar(max)", nullable: false), + Nome = table.Column(type: "nvarchar(max)", nullable: false), + Cognome = table.Column(type: "nvarchar(max)", nullable: false), + DataCreazione = table.Column(type: "datetime2", nullable: false), + DataModifica = table.Column(type: "datetime2", nullable: true), + Eliminato = table.Column(type: "bit", nullable: false), + IdUtenteCreazione = table.Column(type: "uniqueidentifier", nullable: true), + IdUtenteModifica = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Utente", x => x.Id); + table.ForeignKey( + name: "FK_Utente_Utente_IdUtenteCreazione", + column: x => x.IdUtenteCreazione, + principalTable: "Utente", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Utente_Utente_IdUtenteModifica", + column: x => x.IdUtenteModifica, + principalTable: "Utente", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Utente_IdUtenteCreazione", + table: "Utente", + column: "IdUtenteCreazione"); + + migrationBuilder.CreateIndex( + name: "IX_Utente_IdUtenteModifica", + table: "Utente", + column: "IdUtenteModifica"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationUsers"); + + migrationBuilder.DropTable( + name: "Utente"); + } + } +} diff --git a/StandManager.Infrastructure/Migrations/20251201115553_UserApp.Designer.cs b/StandManager.Infrastructure/Migrations/20251201115553_UserApp.Designer.cs new file mode 100644 index 0000000..b743dbd --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201115553_UserApp.Designer.cs @@ -0,0 +1,253 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using StandManager.Infrastructure.DAL.Context; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + [DbContext(typeof(StandManagerDbContext))] + [Migration("20251201115553_UserApp")] + partial class UserApp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Cognome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataCreazione") + .HasColumnType("datetime2"); + + b.Property("DataModifica") + .HasColumnType("datetime2"); + + b.Property("Eliminato") + .HasColumnType("bit"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IdUtenteCreazione") + .HasColumnType("uniqueidentifier"); + + b.Property("IdUtenteModifica") + .HasColumnType("uniqueidentifier"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IdUtenteCreazione"); + + b.HasIndex("IdUtenteModifica"); + + b.ToTable("Utente"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("StandManager.Domain.Entita.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("StandManager.Domain.Entita.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("StandManager.Domain.Entita.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteCreazione") + .WithMany() + .HasForeignKey("IdUtenteCreazione"); + + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteModifica") + .WithMany() + .HasForeignKey("IdUtenteModifica"); + + b.Navigation("UtenteCreazione"); + + b.Navigation("UtenteModifica"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/StandManager.Infrastructure/Migrations/20251201115553_UserApp.cs b/StandManager.Infrastructure/Migrations/20251201115553_UserApp.cs new file mode 100644 index 0000000..0850dd5 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201115553_UserApp.cs @@ -0,0 +1,224 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + /// + public partial class UserApp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_ApplicationUsers", + table: "ApplicationUsers"); + + migrationBuilder.RenameTable( + name: "ApplicationUsers", + newName: "AspNetUsers"); + + migrationBuilder.AlterColumn( + name: "UserName", + table: "AspNetUsers", + type: "nvarchar(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedUserName", + table: "AspNetUsers", + type: "nvarchar(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "AspNetUsers", + type: "nvarchar(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Email", + table: "AspNetUsers", + type: "nvarchar(256)", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_AspNetUsers", + table: "AspNetUsers", + column: "Id"); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", 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: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", 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: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", 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: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropPrimaryKey( + name: "PK_AspNetUsers", + table: "AspNetUsers"); + + migrationBuilder.DropIndex( + name: "EmailIndex", + table: "AspNetUsers"); + + migrationBuilder.DropIndex( + name: "UserNameIndex", + table: "AspNetUsers"); + + migrationBuilder.RenameTable( + name: "AspNetUsers", + newName: "ApplicationUsers"); + + migrationBuilder.AlterColumn( + name: "UserName", + table: "ApplicationUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedUserName", + table: "ApplicationUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "ApplicationUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Email", + table: "ApplicationUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_ApplicationUsers", + table: "ApplicationUsers", + column: "Id"); + } + } +} diff --git a/StandManager.Infrastructure/Migrations/20251201141435_removedUser.Designer.cs b/StandManager.Infrastructure/Migrations/20251201141435_removedUser.Designer.cs new file mode 100644 index 0000000..62252c9 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201141435_removedUser.Designer.cs @@ -0,0 +1,95 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using StandManager.Infrastructure.DAL.Context; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + [DbContext(typeof(StandManagerDbContext))] + [Migration("20251201141435_removedUser")] + partial class removedUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Cognome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataCreazione") + .HasColumnType("datetime2"); + + b.Property("DataModifica") + .HasColumnType("datetime2"); + + b.Property("Eliminato") + .HasColumnType("bit"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IdUtenteCreazione") + .HasColumnType("uniqueidentifier"); + + b.Property("IdUtenteModifica") + .HasColumnType("uniqueidentifier"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IdUtenteCreazione"); + + b.HasIndex("IdUtenteModifica"); + + b.ToTable("Utente"); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteCreazione") + .WithMany() + .HasForeignKey("IdUtenteCreazione"); + + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteModifica") + .WithMany() + .HasForeignKey("IdUtenteModifica"); + + b.Navigation("UtenteCreazione"); + + b.Navigation("UtenteModifica"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/StandManager.Infrastructure/Migrations/20251201141435_removedUser.cs b/StandManager.Infrastructure/Migrations/20251201141435_removedUser.cs new file mode 100644 index 0000000..8782492 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/20251201141435_removedUser.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + /// + public partial class removedUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + LockoutEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + 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: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", 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: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", 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_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + } +} diff --git a/StandManager.Infrastructure/Migrations/StandManagerDbContextModelSnapshot.cs b/StandManager.Infrastructure/Migrations/StandManagerDbContextModelSnapshot.cs new file mode 100644 index 0000000..b85a771 --- /dev/null +++ b/StandManager.Infrastructure/Migrations/StandManagerDbContextModelSnapshot.cs @@ -0,0 +1,92 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using StandManager.Infrastructure.DAL.Context; + +#nullable disable + +namespace StandManager.Infrastructure.Migrations +{ + [DbContext(typeof(StandManagerDbContext))] + partial class StandManagerDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Cognome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataCreazione") + .HasColumnType("datetime2"); + + b.Property("DataModifica") + .HasColumnType("datetime2"); + + b.Property("Eliminato") + .HasColumnType("bit"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IdUtenteCreazione") + .HasColumnType("uniqueidentifier"); + + b.Property("IdUtenteModifica") + .HasColumnType("uniqueidentifier"); + + b.Property("Nome") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IdUtenteCreazione"); + + b.HasIndex("IdUtenteModifica"); + + b.ToTable("Utente"); + }); + + modelBuilder.Entity("StandManager.Domain.Entita.Utente", b => + { + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteCreazione") + .WithMany() + .HasForeignKey("IdUtenteCreazione"); + + b.HasOne("StandManager.Domain.Entita.Utente", "UtenteModifica") + .WithMany() + .HasForeignKey("IdUtenteModifica"); + + b.Navigation("UtenteCreazione"); + + b.Navigation("UtenteModifica"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/StandManager.Infrastructure/StandManager.Infrastructure.csproj b/StandManager.Infrastructure/StandManager.Infrastructure.csproj index f37d9eb..8c26c1e 100644 --- a/StandManager.Infrastructure/StandManager.Infrastructure.csproj +++ b/StandManager.Infrastructure/StandManager.Infrastructure.csproj @@ -8,11 +8,12 @@ - + - + + all @@ -31,6 +32,9 @@ + + ..\Libs\OAService.Domain.dll + ..\Libs\OAService.Infrastructure.dll diff --git a/StandManager.Service/Interfaces/IManagerService.cs b/StandManager.Service/Interfaces/IManagerService.cs index 581d8da..67291b0 100644 --- a/StandManager.Service/Interfaces/IManagerService.cs +++ b/StandManager.Service/Interfaces/IManagerService.cs @@ -1,6 +1,7 @@ -namespace StandManager.Service.Interfaces; - -public interface IManagerService +namespace StandManager.Service.Interfaces { - public IUtenteService UtenteService { get; set; } -} \ No newline at end of file + public interface IManagerService + { + public IUtenteService UtenteService { get; set; } + } +} diff --git a/StandManager.Service/Interfaces/IMembershipService.cs b/StandManager.Service/Interfaces/IMembershipService.cs new file mode 100644 index 0000000..c24b989 --- /dev/null +++ b/StandManager.Service/Interfaces/IMembershipService.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; + +namespace StandManager.Service.Interfaces +{ + // public interface IMembershipService + // { + // Task TryLoginAsync(LoginModel model); + // } + + // public record LoginResult(bool Success, string? ErrorMessage, ClaimsPrincipal? ClaimsPrincipal) + // { + // public static LoginResult Fail(string msg) => new(false, msg, null); + // public static LoginResult Success(ClaimsPrincipal cp) => new(true, null, cp); + // } +} diff --git a/StandManager.Service/Interfaces/IUtenteService.cs b/StandManager.Service/Interfaces/IUtenteService.cs index 56a4b0c..8835c99 100644 --- a/StandManager.Service/Interfaces/IUtenteService.cs +++ b/StandManager.Service/Interfaces/IUtenteService.cs @@ -6,4 +6,4 @@ namespace StandManager.Service.Interfaces; public interface IUtenteService : ITService { -} \ No newline at end of file +} diff --git a/StandManager.Service/ManagerService.cs b/StandManager.Service/ManagerService.cs index 27ab57a..a7b0141 100644 --- a/StandManager.Service/ManagerService.cs +++ b/StandManager.Service/ManagerService.cs @@ -1,4 +1,4 @@ -using StandManager.Service.Interfaces; +using StandManager.Service.Interfaces; namespace StandManager.Service; diff --git a/StandManager.Service/Repository/StandManagerGenericRepository.cs b/StandManager.Service/Repository/StandManagerGenericRepository.cs index 525421c..0190d2b 100644 --- a/StandManager.Service/Repository/StandManagerGenericRepository.cs +++ b/StandManager.Service/Repository/StandManagerGenericRepository.cs @@ -1,3 +1,4 @@ +using OAService.Infrastructure.DAL.Context; using OAService.Infrastructure.DAL.Repository; using StandManager.Infrastructure.DAL.Context; diff --git a/StandManager.slnx b/StandManager.slnx index e7c82a4..ce4d4cb 100644 --- a/StandManager.slnx +++ b/StandManager.slnx @@ -1,6 +1,6 @@ - - - + + + diff --git a/StandManager/Components/Account/IdentityRedirectManager.cs b/StandManager/Components/Account/IdentityRedirectManager.cs new file mode 100644 index 0000000..693d077 --- /dev/null +++ b/StandManager/Components/Account/IdentityRedirectManager.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Components; +using System.Diagnostics.CodeAnalysis; + +namespace StandManager.Components.Account +{ + internal sealed class IdentityRedirectManager(NavigationManager navigationManager) + { + public const string StatusCookieName = "Identity.StatusMessage"; + + private static readonly CookieBuilder StatusCookieBuilder = new() + { + SameSite = SameSiteMode.Strict, + HttpOnly = true, + IsEssential = true, + MaxAge = TimeSpan.FromSeconds(5), + }; + + [DoesNotReturn] + public void RedirectTo(string? uri) + { + uri ??= ""; + + // Prevent open redirects. + if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) + { + uri = navigationManager.ToBaseRelativePath(uri); + } + + // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. + // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. + navigationManager.NavigateTo(uri); + throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + } + + [DoesNotReturn] + public void RedirectTo(string uri, Dictionary queryParameters) + { + var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); + var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); + RedirectTo(newUri); + } + + [DoesNotReturn] + public void RedirectToWithStatus(string uri, string message, HttpContext context) + { + context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); + RedirectTo(uri); + } + + private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); + + [DoesNotReturn] + public void RedirectToCurrentPage() => RedirectTo(CurrentPath); + + [DoesNotReturn] + public void RedirectToCurrentPageWithStatus(string message, HttpContext context) + => RedirectToWithStatus(CurrentPath, message, context); + } +} diff --git a/StandManager/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs b/StandManager/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs new file mode 100644 index 0000000..a9f3e5f --- /dev/null +++ b/StandManager/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using StandManager.Domain.Entita; +using System.Security.Claims; + +namespace StandManager.Components.Account +{ + internal sealed class IdentityRevalidatingAuthenticationStateProvider( + ILoggerFactory loggerFactory, + IServiceScopeFactory scopeFactory, + IOptions options) + : RevalidatingServerAuthenticationStateProvider(loggerFactory) + { + protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); + + protected override async Task ValidateAuthenticationStateAsync( + AuthenticationState authenticationState, CancellationToken cancellationToken) + { + // Get the user manager from a new scope to ensure it fetches fresh data + await using var scope = scopeFactory.CreateAsyncScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + return await ValidateSecurityStampAsync(userManager, authenticationState.User); + } + + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + { + var user = await userManager.GetUserAsync(principal); + if (user is null) + { + return false; + } + else if (!userManager.SupportsUserSecurityStamp) + { + return true; + } + else + { + var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); + var userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; + } + } + } +} diff --git a/StandManager/Components/Account/IdentityUserAccessor.cs b/StandManager/Components/Account/IdentityUserAccessor.cs new file mode 100644 index 0000000..d7f0838 --- /dev/null +++ b/StandManager/Components/Account/IdentityUserAccessor.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using StandManager.Domain.Entita; + +namespace StandManager.Components.Account +{ + internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) + { + public async Task GetRequiredUserAsync(HttpContext context) + { + var user = await userManager.GetUserAsync(context.User); + + if (user is null) + { + redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); + } + + return user; + } + } +} diff --git a/StandManager/Components/App.razor b/StandManager/Components/App.razor index cf1d327..6062600 100644 --- a/StandManager/Components/App.razor +++ b/StandManager/Components/App.razor @@ -3,9 +3,9 @@ - - - + + + @@ -17,14 +17,16 @@ - + - +
+ +
- + diff --git a/StandManager/Components/Layout/MainLayout.razor b/StandManager/Components/Layout/MainLayout.razor index c6c83bf..6e6f901 100644 --- a/StandManager/Components/Layout/MainLayout.razor +++ b/StandManager/Components/Layout/MainLayout.razor @@ -1,76 +1,79 @@ @using Microsoft.AspNetCore.Authentication @inherits LayoutComponentBase - - - -
- - +
+@Body @code { [CascadingParameter] public HttpContext httpContext { get; set; } = default; private async Task OnLogoutPressed(MouseEventArgs e) { + var a = "Ciao"; @* await httpContext.SignOutAsync(); *@ } } \ No newline at end of file diff --git a/StandManager/Components/Pages/Account/Login.razor b/StandManager/Components/Pages/Account/Login.razor new file mode 100644 index 0000000..d2a2e31 --- /dev/null +++ b/StandManager/Components/Pages/Account/Login.razor @@ -0,0 +1,90 @@ +@layout PublicLayout +@page "/account/login" + +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Authentication.Cookies +@using Microsoft.AspNetCore.Identity +@using StandManager.Components.Layout +@using StandManager.Domain.Entita +@using StandManager.Infrastructure.DAL.Context +@using StandManager.Model +@using System.Security.Claims +@using StandManager.Service.Interfaces + +@inject StandManagerDbContext dbContext +@inject NavigationManager navi +@inject IHttpContextAccessor HttpContextAccessor +@inject IManagerService _managerService + +
+
+
+
+
+
+

Accedi

+ + + +
+ + + +
+ +
+ + + +
+ + + + @if (!string.IsNullOrEmpty(errorMessage)) + { +
@errorMessage
+ } +
+
+
+
+
+ +@code { + [SupplyParameterFromForm] + private LoginModel? model { get; set; } + + private string? errorMessage { get; set; } + + [CascadingParameter] + public HttpContext? httpContext { get; set; } + + protected override void OnInitialized() => model ??= new(); + + private async Task HandleValidSubmit(EditContext args) + { + var user = await _managerService.UtenteService.RicercaPer(x => x.Email == model.Email); + var hasher = new PasswordHasher(); + + if (user == null || + hasher.VerifyHashedPassword(user, user.Password, model.Password) != PasswordVerificationResult.Success) + { + // credenziali non valide + errorMessage = "Credenziali non valide."; + return; + } + + var claims = new List + { + new Claim(ClaimTypes.Name, user.Email), + new Claim(ClaimTypes.Role, "Admin") + }; + + var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + await httpContext.SignInAsync(principal); + navi.NavigateTo("/management/dashboard"); + } +} diff --git a/StandManager/Components/Pages/Account/Logout.razor b/StandManager/Components/Pages/Account/Logout.razor new file mode 100644 index 0000000..a9d33b1 --- /dev/null +++ b/StandManager/Components/Pages/Account/Logout.razor @@ -0,0 +1,15 @@ +@page "/account/logout" +@using Microsoft.AspNetCore.Authentication +@inject NavigationManager navManager + +@code { + + [CascadingParameter] + public HttpContext? httpContext { get; set; } + + protected override async Task OnInitializedAsync() + { + await httpContext.SignOutAsync(); + navManager.NavigateTo("/"); + } +} diff --git a/StandManager/Components/Pages/Home.razor b/StandManager/Components/Pages/Home.razor index 9001e0b..bfecc1d 100644 --- a/StandManager/Components/Pages/Home.razor +++ b/StandManager/Components/Pages/Home.razor @@ -1,4 +1,7 @@ @page "/" +@rendermode InteractiveServer +@using Microsoft.AspNetCore.Identity +@using StandManager.Domain.Entita Home diff --git a/StandManager/Components/Pages/Management/Dashboard.razor b/StandManager/Components/Pages/Management/Dashboard.razor index f8fd4a3..390a650 100644 --- a/StandManager/Components/Pages/Management/Dashboard.razor +++ b/StandManager/Components/Pages/Management/Dashboard.razor @@ -1,4 +1,5 @@ -@page "/management" +@page "/management/Dashboard" + @using Microsoft.AspNetCore.Authorization @using StandManager.Components.Layout @attribute [Authorize] @@ -10,7 +11,9 @@
-

Home

+ +
Overview
+

Dashboard

@@ -18,5 +21,9 @@ @code { - + override protected void OnInitialized() + { + // Initialization logic can be added here if needed + var a = "CIAO"; + } } \ No newline at end of file diff --git a/StandManager/Components/Pages/Management/Login.razor b/StandManager/Components/Pages/Management/Login.razor index 99c8e9a..95d6b92 100644 --- a/StandManager/Components/Pages/Management/Login.razor +++ b/StandManager/Components/Pages/Management/Login.razor @@ -4,12 +4,18 @@ @using System.Security.Claims @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Identity +@using OAService.Domain.Entita @using StandManager.Components.Layout @using StandManager.Domain.Entita @using StandManager.Model @using StandManager.Service.Interfaces -@inject NavigationManager Nav -@inject IManagerService _managerService +@using Microsoft.AspNetCore.Http +@inject NavigationManager Navigation +@inject IHttpContextAccessor HttpContextAccessor +@inject HttpClient Http +@inject UserManager UserManager +@inject IUserStore UserStore +@inject SignInManager SignInManager
@@ -23,14 +29,14 @@
- - + +
- - + +
@code { - [CascadingParameter] public HttpContext httpContext { get; set; } = default; - [SupplyParameterFromForm] - private LoginModel model { get; set; } - + private LoginModel? model { get; set; } + private string? errorMessage; protected override void OnInitialized() => model ??= new(); private async Task HandleValidSubmit() { - errorMessage = null; + var u = UserManager.Users.FirstOrDefault(); + var result = await SignInManager.CheckPasswordSignInAsync(u, "test123pwd@", lockoutOnFailure: false); - var user = await _managerService.UtenteService.RicercaPer(x => x.Email == model.Email); - var hasher = new PasswordHasher(); - if (user == null || hasher.VerifyHashedPassword(user, user.Password, model.Password) != PasswordVerificationResult.Success) + if (result.Succeeded) { - errorMessage = "Credenziali non valide."; - return; + await SignInManager.SignInAsync(u, isPersistent: true, "standmanager"); } - - List claims = - [ - new(ClaimTypes.Name, user.Email), - new(ClaimTypes.GivenName, user.Nome), - new("Id", user.Id.ToString()) - ]; - ClaimsIdentity identity = new(claims, "standmanager"); - ClaimsPrincipal claimsPrincipal = new(identity); - await httpContext.SignInAsync("standmanager", claimsPrincipal, new AuthenticationProperties - { - IsPersistent = true, - ExpiresUtc = DateTime.UtcNow.AddHours(8) - }); - Nav.NavigateTo("/management"); + Navigation.NavigateTo("management/Dashboard"); + // var response = await Http.PostAsJsonAsync("/management/adminLogin", model); + + // if (!response.IsSuccessStatusCode) + // { + // // leggiamo il messaggio dall'endpoint (opzionale) + // var msg = await response.Content.ReadAsStringAsync(); + // errorMessage = string.IsNullOrWhiteSpace(msg) + // ? "Credenziali non valide." + // : msg; + + // return; + // } + // var u = await _managerService.UtenteService.RicercaPer(x => x.Email == model!.Email); + // await SignInManager.SignInAsync(new ApplicationUser(), new AuthenticationProperties { IsPersistent = false }); + // // Login ok → vai al management + // Navigation.NavigateTo("management/Dashboard"); } } \ No newline at end of file diff --git a/StandManager/LoginUtils.cs b/StandManager/LoginUtils.cs new file mode 100644 index 0000000..a942323 --- /dev/null +++ b/StandManager/LoginUtils.cs @@ -0,0 +1,40 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using StandManager.Domain.Entita; +using StandManager.Model; +using StandManager.Service.Interfaces; + +public static class LoginUtils +{ + public static async Task OnLogin(LoginModel model, IManagerService managerService, HttpContext ctx) + { + var user = await managerService.UtenteService.RicercaPer(x => x.Email == model.Email); + var hasher = new PasswordHasher(); + + if (user == null || + hasher.VerifyHashedPassword(user, user.Password, model.Password) != PasswordVerificationResult.Success) + { + // credenziali non valide + return Results.BadRequest("Credenziali non valide."); + } + + List claims = + [ + new(ClaimTypes.Name, user.Email), + new(ClaimTypes.GivenName, user.Nome), + new("Id", user.Id.ToString()) + ]; + + ClaimsIdentity identity = new(claims, "standmanager"); + ClaimsPrincipal claimsPrincipal = new(identity); + + await ctx.SignInAsync("standmanager", claimsPrincipal, new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTime.UtcNow.AddHours(8) + }); + + return Results.Ok(); + } +} \ No newline at end of file diff --git a/StandManager/Program.cs b/StandManager/Program.cs index 6b34f5f..6813bc6 100644 --- a/StandManager/Program.cs +++ b/StandManager/Program.cs @@ -1,13 +1,20 @@ -using System.Reflection; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Radzen; using StandManager.Components; +using StandManager.Components.Account; +using StandManager.Domain.Entita; using StandManager.Infrastructure.DAL.Context; +using StandManager.Model; using StandManager.Service; using StandManager.Service.Interfaces; using StandManager.Service.Repository; using StandManager.Utils; +using System.Reflection; +using System.Security.Claims; var builder = WebApplication.CreateBuilder(args); @@ -16,12 +23,19 @@ builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddCascadingAuthenticationState(); +//builder.Services.AddScoped(); +//builder.Services.AddScoped(); +//builder.Services.AddScoped(); -//Database -var connectionString = builder.Configuration.GetConnectionString("ConnectionString"); -builder.Services.AddDbContext(options => - options.UseSqlServer(connectionString) -); +builder.Services.Configure(options => +{ + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireUppercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequiredLength = 1; // puoi metterla anche a 1 + options.Password.RequiredUniqueChars = 0; +}); //DI var types = Assembly.Load("StandManager.Service").GetTypes(); @@ -31,32 +45,36 @@ foreach (var intfc in allProviderTypes.Where(t => t.IsInterface)) var impl = allProviderTypes.FirstOrDefault(c => c.IsClass && intfc.Name[1..] == c.Name); if (impl != null) builder.Services.AddScoped(intfc, impl); } +//Database +var connectionString = builder.Configuration.GetConnectionString("ConnectionString"); +builder.Services.AddDbContext(options => + options.UseSqlServer(connectionString) +); + builder.Services.AddScoped(typeof(IStandManagerGenericRepository<>), typeof(StandManagerGenericRepository<>)); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddRadzenComponents(); - -builder.Services - .AddAuthentication(options => - { - options.DefaultAuthenticateScheme = "standmanager"; - options.DefaultSignInScheme = "standmanager"; - options.DefaultChallengeScheme = "standmanager"; - }) - .AddCookie("standmanager", options => - { - options.LoginPath = "/management/login"; - options.AccessDeniedPath = "/management/access-denied"; - options.ExpireTimeSpan = TimeSpan.FromHours(8); - options.SlidingExpiration = true; - }); - -builder.Services.AddAuthorization(); - builder.Services.AddHttpContextAccessor(); +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.Cookie.Name = "auth_token"; + options.Cookie.MaxAge = TimeSpan.FromMinutes(30); + options.LoginPath = "/account/login"; + options.AccessDeniedPath = "/access-denied"; + }); +builder.Services.AddAuthorization(); +builder.Services.AddCascadingAuthenticationState(); + +builder.Services.AddDbContext(options => +{ + options.UseSqlServer(connectionString); +}); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -72,11 +90,34 @@ else } app.UseHttpsRedirection(); - app.UseStaticFiles(); + app.UseAntiforgery(); +app.UseAuthentication(); +app.UseAuthorization(); + app.MapRazorComponents() .AddInteractiveServerRenderMode(); +app.Use(async (context, func) => +{ + try + { + await func(); + } + catch (Exception e) + { + var logger = context.RequestServices.GetService>(); + // Log dell'eccezione + logger?.LogError(e, "Si è verificata un'eccezione durante l'elaborazione della richiesta."); + + context.Response.Clear(); + + context.Response.StatusCode = e is BadHttpRequestException badHttpRequestException + ? badHttpRequestException.StatusCode + : 500; + } +}); + await app.RunAsync(); diff --git a/StandManager/StandManager.csproj b/StandManager/StandManager.csproj index aef2b01..872101b 100644 --- a/StandManager/StandManager.csproj +++ b/StandManager/StandManager.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -8,6 +8,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/StandManager/appsettings.json b/StandManager/appsettings.json index e1a013c..30d3494 100644 --- a/StandManager/appsettings.json +++ b/StandManager/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "ConnectionString": "Data Source=192.168.0.233\\SQL2019;Initial Catalog=DAC_Eventi;Persist Security Info=True;User ID=dac_user;Password=KZ4ZrUPzJV;TrustServerCertificate=True" + "ConnectionString": "Data Source=192.168.0.233\\SQL2019;Initial Catalog=DAC_StandManager;Persist Security Info=True;User ID=dac_user;Password=KZ4ZrUPzJV;TrustServerCertificate=True" }, "Logging": { "LogLevel": {