Creata pagina login con Syncfusion
This commit is contained in:
9
.github/copilot-instructions.md
vendored
Normal file
9
.github/copilot-instructions.md
vendored
Normal file
@@ -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
|
||||
@@ -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<ApplicationUser> SignInManager
|
||||
@inject ILogger<Login> Logger
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IdentityRedirectManager RedirectManager
|
||||
|
||||
<PageTitle>Log in</PageTitle>
|
||||
<PageTitle>Accedi - SmartDB</PageTitle>
|
||||
|
||||
<h1>Log in</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<section>
|
||||
<StatusMessage Message="@errorMessage" />
|
||||
<EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login">
|
||||
<div class="login-container">
|
||||
<style>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
padding: 3rem 2.5rem;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #667eea;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 1.75rem;
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 0.95rem;
|
||||
color: #888;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-group-custom {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group-custom label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
::deep .e-input-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::deep .e-input-group input.e-field {
|
||||
border: 2px solid #e0e0e0 !important;
|
||||
border-radius: 8px !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
font-size: 0.95rem !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
::deep .e-input-group input.e-field:focus {
|
||||
border-color: #667eea !important;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
|
||||
}
|
||||
|
||||
::deep .e-checkbox-wrapper {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
::deep .e-checkbox-wrapper label {
|
||||
color: #666 !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
::deep .e-btn {
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.5px !important;
|
||||
transition: all 0.3s ease !important;
|
||||
border: none !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
::deep .e-btn.e-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
||||
padding: 0.75rem 1.5rem !important;
|
||||
font-size: 0.95rem !important;
|
||||
min-height: 48px !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
::deep .e-btn.e-primary:hover:not(:disabled) {
|
||||
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4) !important;
|
||||
transform: translateY(-2px) !important;
|
||||
}
|
||||
|
||||
::deep .e-btn.e-primary:disabled {
|
||||
opacity: 0.7 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.login-links {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.login-links a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.login-links a:hover {
|
||||
color: #764ba2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alert-custom {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.alert-danger-custom {
|
||||
background-color: #fee;
|
||||
color: #c33;
|
||||
border-left: 4px solid #c33;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
color: #d32f2f;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.spinner-custom {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: white;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
@@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.login-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 2rem 0;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.login-divider::before,
|
||||
.login-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.login-divider span {
|
||||
padding: 0 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="login-wrapper">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<div class="login-logo">🔐</div>
|
||||
<h1 class="login-title">SmartDB</h1>
|
||||
<p class="login-subtitle">Accedi al tuo account</p>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="alert alert-custom alert-danger-custom">
|
||||
<strong>Errore!</strong> @errorMessage
|
||||
</div>
|
||||
}
|
||||
|
||||
<EditForm Model="Input" FormName="login" OnValidSubmit="LoginUser">
|
||||
<DataAnnotationsValidator />
|
||||
<h2>Use a local account to log in.</h2>
|
||||
<hr />
|
||||
<ValidationSummary class="text-danger" role="alert" />
|
||||
<div class="form-floating mb-3">
|
||||
<InputText @bind-Value="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<ValidationMessage For="() => Input.Email" class="text-danger" />
|
||||
|
||||
<div class="form-group-custom">
|
||||
<label for="email">Email</label>
|
||||
<SfTextBox @bind-Value="Input.Email"
|
||||
id="email"
|
||||
Placeholder="name@example.com"
|
||||
CssClass="form-control-sf"
|
||||
ShowClearButton="true">
|
||||
</SfTextBox>
|
||||
<ValidationMessage For="() => Input.Email" class="validation-error" />
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<InputText type="password" @bind-Value="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<ValidationMessage For="() => Input.Password" class="text-danger" />
|
||||
|
||||
<div class="form-group-custom">
|
||||
<label for="password">Password</label>
|
||||
<SfTextBox @bind-Value="Input.Password"
|
||||
id="password"
|
||||
Placeholder="Inserisci la tua password"
|
||||
CssClass="form-control-sf"
|
||||
TextMode="InputType.Password"
|
||||
ShowClearButton="true">
|
||||
</SfTextBox>
|
||||
<ValidationMessage For="() => Input.Password" class="validation-error" />
|
||||
</div>
|
||||
<div class="checkbox mb-3">
|
||||
<label class="form-label">
|
||||
<InputCheckbox @bind-Value="Input.RememberMe" class="darker-border-checkbox form-check-input" />
|
||||
Remember me
|
||||
</label>
|
||||
|
||||
<div class="form-group-custom">
|
||||
<SfCheckBox @bind-Checked="Input.RememberMe"
|
||||
Label="Ricordami"
|
||||
CssClass="remember-me-checkbox">
|
||||
</SfCheckBox>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
||||
|
||||
<div class="login-button">
|
||||
<SfButton IconCss="e-icon-search"
|
||||
IsPrimary="true"
|
||||
Disabled="@isSubmitting"
|
||||
OnClick="async () => await LoginUser()">
|
||||
@if (isSubmitting)
|
||||
{
|
||||
<span class="spinner-custom"></span>
|
||||
<span>Accesso in corso...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Accedi</span>
|
||||
}
|
||||
</SfButton>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div class="login-divider">
|
||||
<span>Supporto</span>
|
||||
</div>
|
||||
|
||||
<div class="login-links">
|
||||
<p>
|
||||
<a href="Account/ForgotPassword">Forgot your password?</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">Register as a new user</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="Account/ResendEmailConfirmation">Resend email confirmation</a>
|
||||
<a href="Account/ForgotPassword">Password dimenticata?</a>
|
||||
</p>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-offset-2">
|
||||
<section>
|
||||
<h3>Use another service to log in.</h3>
|
||||
<hr />
|
||||
<ExternalLoginPicker />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@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; }
|
||||
}
|
||||
}
|
||||
|
||||
41
SmartDB/Components/Admin/Dtos/CreateUserDto.cs
Normal file
41
SmartDB/Components/Admin/Dtos/CreateUserDto.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SmartDB.Components.Admin.Dtos
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO per la creazione di un nuovo utente da parte dell'amministratore
|
||||
/// </summary>
|
||||
public class CreateUserDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Email dell'utente
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "L'email <20> obbligatoria")]
|
||||
[EmailAddress(ErrorMessage = "Inserisci un'email valida")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nome dell'utente
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Il nome <20> obbligatorio")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cognome dell'utente
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Il cognome <20> obbligatorio")]
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Password temporanea per l'utente
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "La password <20> obbligatoria")]
|
||||
[StringLength(100, MinimumLength = 6, ErrorMessage = "La password deve avere almeno 6 caratteri")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Ruolo da assegnare all'utente
|
||||
/// </summary>
|
||||
public string Role { get; set; } = "User";
|
||||
}
|
||||
}
|
||||
265
SmartDB/Components/Admin/Pages/Users.razor
Normal file
265
SmartDB/Components/Admin/Pages/Users.razor
Normal file
@@ -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<Users> Logger
|
||||
|
||||
<PageTitle>Gestione Utenti - Admin</PageTitle>
|
||||
|
||||
<h1>Gestione Utenti</h1>
|
||||
|
||||
@if (!string.IsNullOrEmpty(successMessage))
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
@successMessage
|
||||
<button type="button" class="btn-close" @onclick="@(() => successMessage = string.Empty)" aria-label="Close"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
@errorMessage
|
||||
<button type="button" class="btn-close" @onclick="@(() => errorMessage = string.Empty)" aria-label="Close"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ul class="nav nav-tabs mb-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @(activeTab == "list" ? "active" : "")" @onclick="@(() => activeTab = "list")" type="button" role="tab" aria-selected="@(activeTab == "list")">
|
||||
Elenco Utenti
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @(activeTab == "create" ? "active" : "")" @onclick="@(() => activeTab = "create")" type="button" role="tab" aria-selected="@(activeTab == "create")">
|
||||
Aggiungi Utente
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
@if (activeTab == "list")
|
||||
{
|
||||
<div class="tab-pane fade show active">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Nome</th>
|
||||
<th>Cognome</th>
|
||||
<th>Ruolo</th>
|
||||
<th>Stato</th>
|
||||
<th>Data Creazione</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (users != null && users.Any())
|
||||
{
|
||||
@foreach (var user in users)
|
||||
{
|
||||
<tr>
|
||||
<td>@user.Email</td>
|
||||
<td>@user.FirstName</td>
|
||||
<td>@user.LastName</td>
|
||||
<td>
|
||||
<span class="badge bg-info">@GetUserRole(user.Id)</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (user.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Attivo</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">Disattivo</span>
|
||||
}
|
||||
</td>
|
||||
<td>@user.CreatedAt.ToShortDateString()</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" @onclick="@(() => ToggleUserStatus(user.Id))" title="Cambia stato">
|
||||
@(user.IsActive ? "Disabilita" : "Abilita")
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @onclick="@(() => DeleteUser(user.Id))" title="Elimina">
|
||||
Elimina
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">Nessun utente trovato</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (activeTab == "create")
|
||||
{
|
||||
<div class="tab-pane fade show active">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Aggiungi Nuovo Utente</h3>
|
||||
<EditForm Model="newUser" OnValidSubmit="HandleCreateUser">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary class="text-danger" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<InputText id="email" class="form-control" @bind-Value="newUser.Email" placeholder="utente@example.com" />
|
||||
<ValidationMessage For="@(() => newUser.Email)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="firstName" class="form-label">Nome</label>
|
||||
<InputText id="firstName" class="form-control" @bind-Value="newUser.FirstName" placeholder="Mario" />
|
||||
<ValidationMessage For="@(() => newUser.FirstName)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="lastName" class="form-label">Cognome</label>
|
||||
<InputText id="lastName" class="form-control" @bind-Value="newUser.LastName" placeholder="Rossi" />
|
||||
<ValidationMessage For="@(() => newUser.LastName)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password Temporanea</label>
|
||||
<InputText type="password" id="password" class="form-control" @bind-Value="newUser.Password" placeholder="Min 6 caratteri" />
|
||||
<ValidationMessage For="@(() => newUser.Password)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Ruolo</label>
|
||||
<InputSelect id="role" class="form-control" @bind-Value="newUser.Role">
|
||||
<option value="User">Utente</option>
|
||||
<option value="Admin">Amministratore</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" disabled="@isSubmitting">
|
||||
@if (isSubmitting)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||
<span>Creazione in corso...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Crea Utente</span>
|
||||
}
|
||||
</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string activeTab = "list";
|
||||
private List<ApplicationUser>? users;
|
||||
private CreateUserDto newUser = new();
|
||||
private string successMessage = string.Empty;
|
||||
private string errorMessage = string.Empty;
|
||||
private bool isSubmitting = false;
|
||||
private Dictionary<string, string> 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<bool> JsConfirm(string message)
|
||||
{
|
||||
// Placeholder - in un componente reale userebbero JS interop
|
||||
return true;
|
||||
}
|
||||
}
|
||||
155
SmartDB/Components/Admin/Services/UserManagementService.cs
Normal file
155
SmartDB/Components/Admin/Services/UserManagementService.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using SmartDB.Components.Admin.Dtos;
|
||||
using SmartDB.Data;
|
||||
|
||||
namespace SmartDB.Components.Admin.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio per la gestione degli utenti dell'applicazione
|
||||
/// </summary>
|
||||
public interface IUserManagementService
|
||||
{
|
||||
/// <summary>
|
||||
/// Crea un nuovo utente con il ruolo specificato
|
||||
/// </summary>
|
||||
Task<(bool Success, string Message)> CreateUserAsync(CreateUserDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Recupera tutti gli utenti
|
||||
/// </summary>
|
||||
Task<List<ApplicationUser>> GetAllUsersAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Elimina un utente
|
||||
/// </summary>
|
||||
Task<(bool Success, string Message)> DeleteUserAsync(string userId);
|
||||
|
||||
/// <summary>
|
||||
/// Disabilita/abilita un utente
|
||||
/// </summary>
|
||||
Task<(bool Success, string Message)> ToggleUserStatusAsync(string userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementazione del servizio di gestione degli utenti
|
||||
/// </summary>
|
||||
public class UserManagementService : IUserManagementService
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly RoleManager<IdentityRole> _roleManager;
|
||||
|
||||
public UserManagementService(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
}
|
||||
|
||||
public async Task<(bool Success, string Message)> CreateUserAsync(CreateUserDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verifica se l'email <20> gi<67> in uso
|
||||
var existingUser = await _userManager.FindByEmailAsync(dto.Email);
|
||||
if (existingUser != null)
|
||||
{
|
||||
return (false, "Un utente con questa email esiste gi<67>");
|
||||
}
|
||||
|
||||
// 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<List<ApplicationUser>> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
SmartDB/Components/Admin/_Imports.razor
Normal file
11
SmartDB/Components/Admin/_Imports.razor
Normal file
@@ -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
|
||||
@@ -7,6 +7,7 @@
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="SmartDB.styles.css" />
|
||||
<link href="_content/Syncfusion.Blazor.Core/styles/bootstrap5.css" rel="stylesheet" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,7 +1,67 @@
|
||||
@page "/"
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using System.Security.Claims
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
<PageTitle>Home - SmartDB</PageTitle>
|
||||
|
||||
Welcome to your new app.
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<h1>Benvenuto in SmartDB</h1>
|
||||
<p class="lead">Applicazione di gestione avanzata</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p class="card-text">Utente: <strong>@currentUserEmail</strong></p>
|
||||
<p class="card-text text-muted">@currentUserName</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isAdmin)
|
||||
{
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h4 class="alert-heading">Pannello Amministratore</h4>
|
||||
<p>Hai accesso alle funzioni di amministrazione.</p>
|
||||
<hr />
|
||||
<a href="/admin/users" class="btn btn-sm btn-primary">Gestisci Utenti</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Dashboard</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Benvenuto nell'applicazione SmartDB. Qui troverai tutti gli strumenti necessari per gestire i tuoi dati.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using SmartDB
|
||||
@using SmartDB.Components
|
||||
@using Syncfusion.Blazor
|
||||
|
||||
@@ -1,9 +1,39 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace SmartDB.Data
|
||||
{
|
||||
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser>(options)
|
||||
/// <summary>
|
||||
/// Context di Entity Framework per l'applicazione SmartDB
|
||||
/// </summary>
|
||||
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser, IdentityRole, string>(options)
|
||||
{
|
||||
/// <summary>
|
||||
/// Configura il modello del database
|
||||
/// </summary>
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
// Seed dei ruoli di sistema
|
||||
var adminRoleId = "1";
|
||||
var userRoleId = "2";
|
||||
|
||||
builder.Entity<IdentityRole>().HasData(
|
||||
new IdentityRole
|
||||
{
|
||||
Id = adminRoleId,
|
||||
Name = "Admin",
|
||||
NormalizedName = "ADMIN"
|
||||
},
|
||||
new IdentityRole
|
||||
{
|
||||
Id = userRoleId,
|
||||
Name = "User",
|
||||
NormalizedName = "USER"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,29 @@ using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace SmartDB.Data
|
||||
{
|
||||
// Add profile data for application users by adding properties to the ApplicationUser class
|
||||
/// <summary>
|
||||
/// Estende IdentityUser con propriet<65> aggiuntive per il profilo dell'applicazione
|
||||
/// </summary>
|
||||
public class ApplicationUser : IdentityUser
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Nome dell'utente
|
||||
/// </summary>
|
||||
public string? FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cognome dell'utente
|
||||
/// </summary>
|
||||
public string? LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica se l'utente <20> attivo
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Data di creazione dell'utente
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
305
SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs
generated
Normal file
305
SmartDB/Data/Migrations/20251127211042_AddUserPropertiesAndRoles.Designer.cs
generated
Normal file
@@ -0,0 +1,305 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("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<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SmartDB.Data.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("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<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("SmartDB.Data.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("SmartDB.Data.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
|
||||
{
|
||||
b.HasOne("SmartDB.Data.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace SmartDB.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUserPropertiesAndRoles : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "AspNetUsers",
|
||||
type: "datetime2",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FirstName",
|
||||
table: "AspNetUsers",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsActive",
|
||||
table: "AspNetUsers",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
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" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
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<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("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<string>("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<string>", b =>
|
||||
@@ -220,6 +169,83 @@ namespace SmartDB.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SmartDB.Data.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("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<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
|
||||
@@ -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<IdentityUserAccessor>();
|
||||
builder.Services.AddScoped<IdentityRedirectManager>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
|
||||
|
||||
// Aggiungi servizi di gestione
|
||||
builder.Services.AddScoped<IUserManagementService, UserManagementService>();
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = IdentityConstants.ApplicationScheme;
|
||||
@@ -28,13 +37,19 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(connectionString));
|
||||
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
|
||||
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
|
||||
.AddRoles<IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddSignInManager()
|
||||
.AddRoleManager<RoleManager<IdentityRole>>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
|
||||
|
||||
// Aggiungi Authorization
|
||||
builder.Services.AddAuthorizationBuilder()
|
||||
.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.22" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.22" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.22" />
|
||||
<PackageReference Include="Syncfusion.Blazor" Version="28.1.33" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user