Template
mirror of
https://github.com/egmont11/ASP.Net-Core-MVC-Template.git
synced 2026-07-24 07:09:56 +02:00
Added some GDPR things (data export, deletion, edit)
This commit is contained in:
@@ -44,9 +44,43 @@ public class AuthController : Controller
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Profile()
|
||||
{
|
||||
var userResult = await _authService.FindUserByEmailOrUsernameAsync(User.Identity?.Name ?? "");
|
||||
if (!userResult.Success || userResult.Data == null) return RedirectToAction("Login");
|
||||
|
||||
var model = new ProfileViewModel
|
||||
{
|
||||
UserName = userResult.Data.UserName,
|
||||
Email = userResult.Data.Email
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region httpPost
|
||||
[HttpPost] [Authorize] [ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Profile(ProfileViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid) return View(model);
|
||||
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId)) return RedirectToAction("Login");
|
||||
|
||||
var result = await _authService.UpdateProfileAsync(userId, model);
|
||||
if (!result.Success)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, result.Message);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
TempData["SuccessMessage"] = "Profile updated successfully. Please log in again if you changed your username.";
|
||||
return RedirectToAction("Profile");
|
||||
}
|
||||
|
||||
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Login(AuthLoginViewModel model)
|
||||
{
|
||||
@@ -136,5 +170,21 @@ public class AuthController : Controller
|
||||
|
||||
return RedirectToAction("Register");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> DownloadData()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId)) return RedirectToAction("Login");
|
||||
|
||||
var result = await _authService.GetUserDataAsync(userId);
|
||||
if (!result.Success || result.Data == null) return BadRequest(result.Message);
|
||||
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(result.Data, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
|
||||
return File(bytes, "application/json", $"user_data_{User.Identity?.Name}.json");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ public interface IAuthService
|
||||
Task<ServiceResult<UserModel>> RegisterAsync(AuthRegisterViewModel model);
|
||||
Task LogoutAsync();
|
||||
Task<ServiceResult> DeleteUserAsync(UserModel user);
|
||||
Task<ServiceResult> UpdateProfileAsync(int userId, ProfileViewModel model);
|
||||
Task<ServiceResult<object>> GetUserDataAsync(int userId);
|
||||
}
|
||||
|
||||
@@ -132,4 +132,52 @@ public class AuthService : IAuthService
|
||||
return ServiceResult.Failure("An error occurred while deleting the user.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ServiceResult> UpdateProfileAsync(int userId, ProfileViewModel model)
|
||||
{
|
||||
var user = await _context.Users.FindAsync(userId);
|
||||
if (user == null) return ServiceResult.Failure("User not found.");
|
||||
|
||||
var isTaken = await _context.Users.AnyAsync(u => u.Id != userId &&
|
||||
(u.UserName.ToLower() == model.UserName.Trim().ToLower() ||
|
||||
u.Email.ToLower() == model.Email.Trim().ToLower()));
|
||||
|
||||
if (isTaken) return ServiceResult.Failure("Username or Email already exists.");
|
||||
|
||||
user.UserName = model.UserName.Trim();
|
||||
user.Email = model.Email.Trim().ToLower();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(model.NewPassword))
|
||||
{
|
||||
user.Password = BCrypt.Net.BCrypt.HashPassword(model.NewPassword);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
return ServiceResult.Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating profile for user {UserId}", userId);
|
||||
return ServiceResult.Failure("An error occurred while saving changes.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ServiceResult<object>> GetUserDataAsync(int userId)
|
||||
{
|
||||
var user = await _context.Users.FindAsync(userId);
|
||||
if (user == null) return ServiceResult<object>.Failure("User not found.");
|
||||
|
||||
var data = new
|
||||
{
|
||||
user.Id,
|
||||
user.UserName,
|
||||
user.Email,
|
||||
Role = user.Role.ToString(),
|
||||
ExportDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return ServiceResult<object>.Ok(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace TemplateWeb.Models;
|
||||
|
||||
public class ProfileViewModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Username")]
|
||||
[StringLength(50, MinimumLength = 3)]
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New Password (leave blank to keep current)")]
|
||||
[MinLength(5)]
|
||||
public string? NewPassword { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm New Password")]
|
||||
[Compare("NewPassword")]
|
||||
public string? ConfirmPassword { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@model ProfileViewModel
|
||||
@{
|
||||
ViewData["Title"] = "My Profile";
|
||||
}
|
||||
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6">
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-success">@TempData["SuccessMessage"]</div>
|
||||
}
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<h1 class="card-title text-center mb-4">My Profile</h1>
|
||||
|
||||
<form asp-action="Profile">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="UserName" class="form-label"></label>
|
||||
<input asp-for="UserName" class="form-control" />
|
||||
<span asp-validation-for="UserName" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Email" class="form-label"></label>
|
||||
<input asp-for="Email" class="form-control" />
|
||||
<span asp-validation-for="Email" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<p class="text-muted small">Change Password (optional)</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="NewPassword" class="form-label"></label>
|
||||
<input asp-for="NewPassword" class="form-control" />
|
||||
<span asp-validation-for="NewPassword" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="ConfirmPassword" class="form-label"></label>
|
||||
<input asp-for="ConfirmPassword" class="form-control" />
|
||||
<span asp-validation-for="ConfirmPassword" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="d-grid mt-4">
|
||||
<button type="submit" class="btn btn-primary">Update Profile</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-danger shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="text-danger">Privacy & Data</h3>
|
||||
<p class="text-muted small">Manage your personal data and account status.</p>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a asp-action="DownloadData" class="btn btn-outline-info">Download My Data (JSON)</a>
|
||||
|
||||
<form asp-action="DeleteAccount" method="post" onsubmit="return confirm('Are you absolutely sure you want to delete your account? This action cannot be undone.');">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-outline-danger">Delete My Account</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
||||
}
|
||||
@@ -30,28 +30,33 @@
|
||||
<ul class="navbar-nav">
|
||||
@if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle text-dark" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<strong>@User.Identity.Name</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
@if (User.IsInRole("Admin"))
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-primary fw-bold" asp-area="Admin" asp-controller="Dashboard" asp-action="Index">Admin Panel</a>
|
||||
</li>
|
||||
<li><a class="dropdown-item text-primary fw-bold" asp-area="Admin" asp-controller="Dashboard" asp-action="Index">Admin Panel</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
}
|
||||
<li class="nav-item">
|
||||
<span class="nav-link text-dark">Hello, @User.Identity.Name!</span>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<form asp-controller="Auth" asp-action="Logout" method="post" class="form-inline">
|
||||
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
|
||||
<li><a class="dropdown-item" asp-area="" asp-controller="Auth" asp-action="Profile">My Profile</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form asp-area="" asp-controller="Auth" asp-action="Logout" method="post" class="dropdown-item p-0">
|
||||
<button type="submit" class="btn btn-link dropdown-item">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-controller="Auth" asp-action="Register">Register</a>
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Auth" asp-action="Register">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-controller="Auth" asp-action="Login">Login</a>
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Auth" asp-action="Login">Login</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user