password reset added

This commit is contained in:
2026-06-28 08:22:40 -04:00
parent 53f7a9f8f1
commit ba1ef080ad
4 changed files with 120 additions and 1 deletions

View File

@@ -1,4 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace SportsDivision.Application.DTOs; namespace SportsDivision.Application.DTOs;
public class UserDto { public string Id { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string FullName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } public string? SchoolName { get; set; } public bool IsActive { get; set; } } public class UserDto { public string Id { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string FullName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } public string? SchoolName { get; set; } public bool IsActive { get; set; } }
public class UserCreateDto { public string Email { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } } public class UserCreateDto { public string Email { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } }
public class UserUpdateDto { public string Id { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } public bool IsActive { get; set; } } public class UserUpdateDto { public string Id { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string Role { get; set; } = string.Empty; public int? SchoolId { get; set; } public bool IsActive { get; set; } }
public class ResetPasswordDto
{
public string Id { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
[Required(ErrorMessage = "New password is required.")]
[DataType(DataType.Password)]
[Display(Name = "New Password")]
public string NewPassword { get; set; } = string.Empty;
[Required(ErrorMessage = "Please confirm the new password.")]
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare(nameof(NewPassword), ErrorMessage = "Passwords do not match.")]
public string ConfirmPassword { get; set; } = string.Empty;
}

View File

@@ -202,6 +202,51 @@ public class UserManagementController : Controller
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
} }
[HttpGet]
public async Task<IActionResult> ResetPassword(string id)
{
var user = await _userManager.FindByIdAsync(id);
if (user == null) return NotFound();
return View(new ResetPasswordDto
{
Id = user.Id,
Email = user.Email ?? string.Empty,
FullName = user.FullName
});
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetPassword(ResetPasswordDto dto)
{
var user = await _userManager.FindByIdAsync(dto.Id);
if (user == null) return NotFound();
// Keep display fields populated regardless of the posted values.
dto.Email = user.Email ?? string.Empty;
dto.FullName = user.FullName;
if (!ModelState.IsValid)
{
return View(dto);
}
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var result = await _userManager.ResetPasswordAsync(user, token, dto.NewPassword);
if (!result.Succeeded)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return View(dto);
}
TempData["SuccessMessage"] = $"Password reset successfully for {user.Email}.";
return RedirectToAction(nameof(Index));
}
private async Task PopulateDropdowns() private async Task PopulateDropdowns()
{ {
ViewBag.Roles = await _roleManager.Roles.Select(r => r.Name).ToListAsync(); ViewBag.Roles = await _roleManager.Roles.Select(r => r.Name).ToListAsync();

View File

@@ -53,7 +53,7 @@
<th>Role</th> <th>Role</th>
<th>School</th> <th>School</th>
<th>Status</th> <th>Status</th>
<th style="width: 160px;">Actions</th> <th style="width: 200px;">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -78,6 +78,9 @@
<a asp-action="Edit" asp-route-id="@user.Id" class="btn btn-sm btn-outline-primary" title="Edit"> <a asp-action="Edit" asp-route-id="@user.Id" class="btn btn-sm btn-outline-primary" title="Edit">
<i class="bi bi-pencil"></i> <i class="bi bi-pencil"></i>
</a> </a>
<a asp-action="ResetPassword" asp-route-id="@user.Id" class="btn btn-sm btn-outline-secondary" title="Reset Password">
<i class="bi bi-key"></i>
</a>
<form asp-action="ToggleActive" asp-route-id="@user.Id" method="post" class="d-inline"> <form asp-action="ToggleActive" asp-route-id="@user.Id" method="post" class="d-inline">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
@if (user.IsActive) @if (user.IsActive)

View File

@@ -0,0 +1,52 @@
@model ResetPasswordDto
@{
ViewData["Title"] = "Reset Password";
}
<h2 class="page-header">
<i class="bi bi-key me-2"></i>Reset Password
</h2>
<div class="row">
<div class="col-lg-6">
<div class="card shadow-sm">
<div class="card-body">
<p class="text-muted">
Set a new password for <strong>@Model.FullName</strong> (@Model.Email).
The user will need to sign in with this new password.
</p>
<form asp-action="ResetPassword" method="post">
@Html.AntiForgeryToken()
<input asp-for="Id" type="hidden" />
<input asp-for="Email" type="hidden" />
<input asp-for="FullName" type="hidden" />
<div asp-validation-summary="All" class="text-danger mb-3"></div>
<div class="mb-3">
<label asp-for="NewPassword" class="form-label">New Password</label>
<input asp-for="NewPassword" class="form-control" type="password" autocomplete="new-password" required />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="ConfirmPassword" class="form-label">Confirm Password</label>
<input asp-for="ConfirmPassword" class="form-control" type="password" autocomplete="new-password" required />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-key me-1"></i>Reset Password
</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}