From 9369b70e2d3a4b7ad0f95dbd7093f959cc8985f0 Mon Sep 17 00:00:00 2001 From: SmartRootsSrl Date: Thu, 27 Nov 2025 22:23:17 +0100 Subject: [PATCH] Creata pagina login con Syncfusion --- .github/copilot-instructions.md | 9 + SmartDB/Components/Account/Pages/Login.razor | 361 ++++++++++++++---- .../Components/Admin/Dtos/CreateUserDto.cs | 41 ++ SmartDB/Components/Admin/Pages/Users.razor | 265 +++++++++++++ .../Admin/Services/UserManagementService.cs | 155 ++++++++ SmartDB/Components/Admin/_Imports.razor | 11 + SmartDB/Components/App.razor | 1 + SmartDB/Components/Pages/Home.razor | 66 +++- SmartDB/Components/_Imports.razor | 1 + SmartDB/Data/ApplicationDbContext.cs | 32 +- SmartDB/Data/ApplicationUser.cs | 24 +- ...1042_AddUserPropertiesAndRoles.Designer.cs | 305 +++++++++++++++ ...0251127211042_AddUserPropertiesAndRoles.cs | 82 ++++ .../ApplicationDbContextModelSnapshot.cs | 160 ++++---- SmartDB/Program.cs | 17 +- SmartDB/SmartDB.csproj | 1 + 16 files changed, 1389 insertions(+), 142 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 SmartDB/Components/Admin/Dtos/CreateUserDto.cs create mode 100644 SmartDB/Components/Admin/Pages/Users.razor create mode 100644 SmartDB/Components/Admin/Services/UserManagementService.cs create mode 100644 SmartDB/Components/Admin/_Imports.razor create mode 100644 SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs create mode 100644 SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..c142937 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,9 @@ +- Always use controllers for API endpoints +- Always use dependency injection +- Use the repository pattern +- Add XML summary comments +- Use DTOs where it makes sense +- Use separate files for each class, interface, and DTO +- Always show a plan first before writing code +- Follow best practices and clean code principles +- When refered to Syncfusion Blazor components , always use syncfusion-blazor-assistant mcp \ No newline at end of file diff --git a/SmartDB/Components/Account/Pages/Login.razor b/SmartDB/Components/Account/Pages/Login.razor index e2374e3..a8a34db 100644 --- a/SmartDB/Components/Account/Pages/Login.razor +++ b/SmartDB/Components/Account/Pages/Login.razor @@ -4,125 +4,350 @@ @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Identity @using SmartDB.Data +@using Syncfusion.Blazor.Inputs +@using Syncfusion.Blazor.Buttons @inject SignInManager SignInManager @inject ILogger Logger @inject NavigationManager NavigationManager @inject IdentityRedirectManager RedirectManager -Log in +Accedi - SmartDB -

Log in

-
-
-
- - +
-
-
-
-

Use another service to log in.

-
- -
+
@code { private string? errorMessage; + private bool isSubmitting = false; [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - [SupplyParameterFromQuery] - private string? ReturnUrl { get; set; } - protected override async Task OnInitializedAsync() { if (HttpMethods.IsGet(HttpContext.Request.Method)) { - // Clear the existing external cookie to ensure a clean login process await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); } } public async Task LoginUser() { - // This doesn't count login failures towards account lockout - // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); - if (result.Succeeded) + isSubmitting = true; + errorMessage = null; + + try { - Logger.LogInformation("User logged in."); - RedirectManager.RedirectTo(ReturnUrl); + var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + RedirectManager.RedirectTo("/"); + } + else if (result.IsLockedOut) + { + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + errorMessage = "Email o password non validi. Riprova."; + } } - else if (result.RequiresTwoFactor) + catch (Exception ex) { - RedirectManager.RedirectTo( - "Account/LoginWith2fa", - new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); + Logger.LogError(ex, "Errore durante il login"); + errorMessage = "Errore durante l'accesso. Riprova più tardi."; } - else if (result.IsLockedOut) + finally { - Logger.LogWarning("User account locked out."); - RedirectManager.RedirectTo("Account/Lockout"); - } - else - { - errorMessage = "Error: Invalid login attempt."; + isSubmitting = false; } } - private sealed class InputModel + private class InputModel { - [Required] - [EmailAddress] + [Required(ErrorMessage = "L'email è obbligatoria")] + [EmailAddress(ErrorMessage = "Inserisci un'email valida")] public string Email { get; set; } = ""; - [Required] + [Required(ErrorMessage = "La password è obbligatoria")] [DataType(DataType.Password)] public string Password { get; set; } = ""; - [Display(Name = "Remember me?")] + [Display(Name = "Ricordami?")] public bool RememberMe { get; set; } } } diff --git a/SmartDB/Components/Admin/Dtos/CreateUserDto.cs b/SmartDB/Components/Admin/Dtos/CreateUserDto.cs new file mode 100644 index 0000000..129b249 --- /dev/null +++ b/SmartDB/Components/Admin/Dtos/CreateUserDto.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace SmartDB.Components.Admin.Dtos +{ + /// + /// DTO per la creazione di un nuovo utente da parte dell'amministratore + /// + public class CreateUserDto + { + /// + /// Email dell'utente + /// + [Required(ErrorMessage = "L'email è obbligatoria")] + [EmailAddress(ErrorMessage = "Inserisci un'email valida")] + public string Email { get; set; } = string.Empty; + + /// + /// Nome dell'utente + /// + [Required(ErrorMessage = "Il nome è obbligatorio")] + public string FirstName { get; set; } = string.Empty; + + /// + /// Cognome dell'utente + /// + [Required(ErrorMessage = "Il cognome è obbligatorio")] + public string LastName { get; set; } = string.Empty; + + /// + /// Password temporanea per l'utente + /// + [Required(ErrorMessage = "La password è obbligatoria")] + [StringLength(100, MinimumLength = 6, ErrorMessage = "La password deve avere almeno 6 caratteri")] + public string Password { get; set; } = string.Empty; + + /// + /// Ruolo da assegnare all'utente + /// + public string Role { get; set; } = "User"; + } +} diff --git a/SmartDB/Components/Admin/Pages/Users.razor b/SmartDB/Components/Admin/Pages/Users.razor new file mode 100644 index 0000000..54ec365 --- /dev/null +++ b/SmartDB/Components/Admin/Pages/Users.razor @@ -0,0 +1,265 @@ +@page "/admin/users" +@using Microsoft.AspNetCore.Authorization +@using SmartDB.Components.Admin.Dtos +@using SmartDB.Components.Admin.Services +@using SmartDB.Data +@attribute [Authorize(Policy = "AdminOnly")] + +@inject IUserManagementService UserService +@inject ILogger Logger + +Gestione Utenti - Admin + +

Gestione Utenti

+ +@if (!string.IsNullOrEmpty(successMessage)) +{ + +} + +@if (!string.IsNullOrEmpty(errorMessage)) +{ + +} + + + +
+ @if (activeTab == "list") + { +
+
+ + + + + + + + + + + + + + @if (users != null && users.Any()) + { + @foreach (var user in users) + { + + + + + + + + + + } + } + else + { + + + + } + +
EmailNomeCognomeRuoloStatoData CreazioneAzioni
@user.Email@user.FirstName@user.LastName + @GetUserRole(user.Id) + + @if (user.IsActive) + { + Attivo + } + else + { + Disattivo + } + @user.CreatedAt.ToShortDateString() + + +
Nessun utente trovato
+
+
+ } + + @if (activeTab == "create") + { +
+
+
+

Aggiungi Nuovo Utente

+ + + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + + +
+ + +
+
+
+
+ } +
+ +@code { + private string activeTab = "list"; + private List? users; + private CreateUserDto newUser = new(); + private string successMessage = string.Empty; + private string errorMessage = string.Empty; + private bool isSubmitting = false; + private Dictionary userRoles = new(); + + protected override async Task OnInitializedAsync() + { + await LoadUsers(); + } + + private async Task LoadUsers() + { + try + { + users = await UserService.GetAllUsersAsync(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento degli utenti"); + errorMessage = "Errore nel caricamento degli utenti"; + } + } + + private async Task HandleCreateUser() + { + isSubmitting = true; + try + { + var (success, message) = await UserService.CreateUserAsync(newUser); + if (success) + { + successMessage = message; + newUser = new(); + await LoadUsers(); + activeTab = "list"; + } + else + { + errorMessage = message; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella creazione dell'utente"); + errorMessage = "Errore nella creazione dell'utente"; + } + finally + { + isSubmitting = false; + } + } + + private async Task DeleteUser(string userId) + { + if (await JsConfirm("Sei sicuro di voler eliminare questo utente?")) + { + var (success, message) = await UserService.DeleteUserAsync(userId); + if (success) + { + successMessage = message; + await LoadUsers(); + } + else + { + errorMessage = message; + } + } + } + + private async Task ToggleUserStatus(string userId) + { + var (success, message) = await UserService.ToggleUserStatusAsync(userId); + if (success) + { + successMessage = message; + await LoadUsers(); + } + else + { + errorMessage = message; + } + } + + private string GetUserRole(string userId) + { + // Per ora returniamo "User" - in futuro implementeremo la logica per recuperare i ruoli + return "User"; + } + + private async Task JsConfirm(string message) + { + // Placeholder - in un componente reale userebbero JS interop + return true; + } +} diff --git a/SmartDB/Components/Admin/Services/UserManagementService.cs b/SmartDB/Components/Admin/Services/UserManagementService.cs new file mode 100644 index 0000000..96eadf8 --- /dev/null +++ b/SmartDB/Components/Admin/Services/UserManagementService.cs @@ -0,0 +1,155 @@ +using Microsoft.AspNetCore.Identity; +using SmartDB.Components.Admin.Dtos; +using SmartDB.Data; + +namespace SmartDB.Components.Admin.Services +{ + /// + /// Servizio per la gestione degli utenti dell'applicazione + /// + public interface IUserManagementService + { + /// + /// Crea un nuovo utente con il ruolo specificato + /// + Task<(bool Success, string Message)> CreateUserAsync(CreateUserDto dto); + + /// + /// Recupera tutti gli utenti + /// + Task> GetAllUsersAsync(); + + /// + /// Elimina un utente + /// + Task<(bool Success, string Message)> DeleteUserAsync(string userId); + + /// + /// Disabilita/abilita un utente + /// + Task<(bool Success, string Message)> ToggleUserStatusAsync(string userId); + } + + /// + /// Implementazione del servizio di gestione degli utenti + /// + public class UserManagementService : IUserManagementService + { + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + + public UserManagementService(UserManager userManager, RoleManager roleManager) + { + _userManager = userManager; + _roleManager = roleManager; + } + + public async Task<(bool Success, string Message)> CreateUserAsync(CreateUserDto dto) + { + try + { + // Verifica se l'email è già in uso + var existingUser = await _userManager.FindByEmailAsync(dto.Email); + if (existingUser != null) + { + return (false, "Un utente con questa email esiste già"); + } + + // Crea il nuovo utente + var user = new ApplicationUser + { + UserName = dto.Email, + Email = dto.Email, + FirstName = dto.FirstName, + LastName = dto.LastName, + EmailConfirmed = true, + IsActive = true + }; + + var result = await _userManager.CreateAsync(user, dto.Password); + if (!result.Succeeded) + { + var errors = string.Join(", ", result.Errors.Select(e => e.Description)); + return (false, $"Errore nella creazione dell'utente: {errors}"); + } + + // Assegna il ruolo + var roleExist = await _roleManager.RoleExistsAsync(dto.Role); + if (!roleExist) + { + dto.Role = "User"; // Fallback al ruolo User + } + + var roleResult = await _userManager.AddToRoleAsync(user, dto.Role); + if (!roleResult.Succeeded) + { + var errors = string.Join(", ", roleResult.Errors.Select(e => e.Description)); + return (false, $"Errore nell'assegnazione del ruolo: {errors}"); + } + + return (true, "Utente creato con successo"); + } + catch (Exception ex) + { + return (false, $"Errore: {ex.Message}"); + } + } + + public async Task> GetAllUsersAsync() + { + return _userManager.Users.ToList(); + } + + public async Task<(bool Success, string Message)> DeleteUserAsync(string userId) + { + try + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return (false, "Utente non trovato"); + } + + var result = await _userManager.DeleteAsync(user); + if (!result.Succeeded) + { + var errors = string.Join(", ", result.Errors.Select(e => e.Description)); + return (false, $"Errore nell'eliminazione: {errors}"); + } + + return (true, "Utente eliminato con successo"); + } + catch (Exception ex) + { + return (false, $"Errore: {ex.Message}"); + } + } + + public async Task<(bool Success, string Message)> ToggleUserStatusAsync(string userId) + { + try + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return (false, "Utente non trovato"); + } + + user.IsActive = !user.IsActive; + var result = await _userManager.UpdateAsync(user); + + if (!result.Succeeded) + { + var errors = string.Join(", ", result.Errors.Select(e => e.Description)); + return (false, $"Errore nell'aggiornamento: {errors}"); + } + + return (true, $"Utente {(user.IsActive ? "abilitato" : "disabilitato")} con successo"); + } + catch (Exception ex) + { + return (false, $"Errore: {ex.Message}"); + } + } + } +} diff --git a/SmartDB/Components/Admin/_Imports.razor b/SmartDB/Components/Admin/_Imports.razor new file mode 100644 index 0000000..0eaaa27 --- /dev/null +++ b/SmartDB/Components/Admin/_Imports.razor @@ -0,0 +1,11 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using SmartDB +@using SmartDB.Components +@using SmartDB.Data diff --git a/SmartDB/Components/App.razor b/SmartDB/Components/App.razor index 8a18b26..5d5f6f1 100644 --- a/SmartDB/Components/App.razor +++ b/SmartDB/Components/App.razor @@ -7,6 +7,7 @@ + diff --git a/SmartDB/Components/Pages/Home.razor b/SmartDB/Components/Pages/Home.razor index 9001e0b..1cb7d29 100644 --- a/SmartDB/Components/Pages/Home.razor +++ b/SmartDB/Components/Pages/Home.razor @@ -1,7 +1,67 @@ @page "/" +@using Microsoft.AspNetCore.Authorization +@using System.Security.Claims +@attribute [Authorize] -Home +@inject AuthenticationStateProvider AuthenticationStateProvider -

Hello, world!

+Home - SmartDB -Welcome to your new app. +
+
+
+

Benvenuto in SmartDB

+

Applicazione di gestione avanzata

+
+
+
+
+

Utente: @currentUserEmail

+

@currentUserName

+
+
+
+
+ + @if (isAdmin) + { + + } + +
+
+
+
+
Dashboard
+
+
+

Benvenuto nell'applicazione SmartDB. Qui troverai tutti gli strumenti necessari per gestire i tuoi dati.

+
+
+
+
+
+ +@code { + private string currentUserEmail = string.Empty; + private string currentUserName = string.Empty; + private bool isAdmin = false; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user.Identity?.IsAuthenticated ?? false) + { + currentUserEmail = user.FindFirst(ClaimTypes.Email)?.Value ?? user.Identity.Name ?? "Utente"; + currentUserName = $"{user.FindFirst(ClaimTypes.GivenName)?.Value} {user.FindFirst(ClaimTypes.Surname)?.Value}".Trim(); + isAdmin = user.IsInRole("Admin"); + } + } +} diff --git a/SmartDB/Components/_Imports.razor b/SmartDB/Components/_Imports.razor index acc1094..43fdde9 100644 --- a/SmartDB/Components/_Imports.razor +++ b/SmartDB/Components/_Imports.razor @@ -9,3 +9,4 @@ @using Microsoft.JSInterop @using SmartDB @using SmartDB.Components +@using Syncfusion.Blazor diff --git a/SmartDB/Data/ApplicationDbContext.cs b/SmartDB/Data/ApplicationDbContext.cs index e8e1da7..08876c1 100644 --- a/SmartDB/Data/ApplicationDbContext.cs +++ b/SmartDB/Data/ApplicationDbContext.cs @@ -1,9 +1,39 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace SmartDB.Data { - public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) + /// + /// Context di Entity Framework per l'applicazione SmartDB + /// + public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) { + /// + /// Configura il modello del database + /// + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + // Seed dei ruoli di sistema + var adminRoleId = "1"; + var userRoleId = "2"; + + builder.Entity().HasData( + new IdentityRole + { + Id = adminRoleId, + Name = "Admin", + NormalizedName = "ADMIN" + }, + new IdentityRole + { + Id = userRoleId, + Name = "User", + NormalizedName = "USER" + } + ); + } } } diff --git a/SmartDB/Data/ApplicationUser.cs b/SmartDB/Data/ApplicationUser.cs index 6d9e87f..909faa9 100644 --- a/SmartDB/Data/ApplicationUser.cs +++ b/SmartDB/Data/ApplicationUser.cs @@ -2,9 +2,29 @@ using Microsoft.AspNetCore.Identity; namespace SmartDB.Data { - // Add profile data for application users by adding properties to the ApplicationUser class + /// + /// Estende IdentityUser con proprietà aggiuntive per il profilo dell'applicazione + /// public class ApplicationUser : IdentityUser { - } + /// + /// Nome dell'utente + /// + public string? FirstName { get; set; } + /// + /// Cognome dell'utente + /// + public string? LastName { get; set; } + + /// + /// Indica se l'utente è attivo + /// + public bool IsActive { get; set; } = true; + + /// + /// Data di creazione dell'utente + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + } } diff --git a/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs b/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs new file mode 100644 index 0000000..218f305 --- /dev/null +++ b/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs @@ -0,0 +1,305 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SmartDB.Data; + +#nullable disable + +namespace SmartDB.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251127211042_AddUserPropertiesAndRoles")] + partial class AddUserPropertiesAndRoles + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.22") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = "1", + Name = "Admin", + NormalizedName = "ADMIN" + }, + new + { + Id = "2", + Name = "User", + NormalizedName = "USER" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", 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("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + 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.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (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("SmartDB.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + 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("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("SmartDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SmartDB.Data.ApplicationUser", 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("SmartDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SmartDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.cs b/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.cs new file mode 100644 index 0000000..af615bc --- /dev/null +++ b/SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace SmartDB.Migrations +{ + /// + public partial class AddUserPropertiesAndRoles : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreatedAt", + table: "AspNetUsers", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "FirstName", + table: "AspNetUsers", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "IsActive", + table: "AspNetUsers", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "LastName", + table: "AspNetUsers", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { "1", null, "Admin", "ADMIN" }, + { "2", null, "User", "USER" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: "1"); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: "2"); + + migrationBuilder.DropColumn( + name: "CreatedAt", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "FirstName", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "IsActive", + table: "AspNetUsers"); + + migrationBuilder.DropColumn( + name: "LastName", + table: "AspNetUsers"); + } + } +} diff --git a/SmartDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SmartDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 19552ee..6fb7611 100644 --- a/SmartDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SmartDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -17,76 +17,11 @@ namespace SmartDB.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("ProductVersion", "8.0.22") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("SmartDB.Data.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("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -112,6 +47,20 @@ namespace SmartDB.Migrations .HasFilter("[NormalizedName] IS NOT NULL"); b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = "1", + Name = "Admin", + NormalizedName = "ADMIN" + }, + new + { + Id = "2", + Name = "User", + NormalizedName = "USER" + }); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -220,6 +169,83 @@ namespace SmartDB.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("SmartDB.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + 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("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) diff --git a/SmartDB/Program.cs b/SmartDB/Program.cs index ae0d714..d782404 100644 --- a/SmartDB/Program.cs +++ b/SmartDB/Program.cs @@ -1,9 +1,12 @@ using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using SmartDB.Components; using SmartDB.Components.Account; +using SmartDB.Components.Admin.Services; using SmartDB.Data; +using Syncfusion.Blazor; var builder = WebApplication.CreateBuilder(args); @@ -11,11 +14,17 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); +// Registra Syncfusion Blazor +builder.Services.AddSyncfusionBlazor(); + builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +// Aggiungi servizi di gestione +builder.Services.AddScoped(); + builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; @@ -28,13 +37,19 @@ builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); -builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) +builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = false) + .AddRoles() .AddEntityFrameworkStores() .AddSignInManager() + .AddRoleManager>() .AddDefaultTokenProviders(); builder.Services.AddSingleton, IdentityNoOpEmailSender>(); +// Aggiungi Authorization +builder.Services.AddAuthorizationBuilder() + .AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/SmartDB/SmartDB.csproj b/SmartDB/SmartDB.csproj index 458f8cc..70f282b 100644 --- a/SmartDB/SmartDB.csproj +++ b/SmartDB/SmartDB.csproj @@ -12,6 +12,7 @@ +