14176a3ee2
Introduce full admin user listing/detail endpoints and a forced password-change flow. Backend: make CurrentUserResponse.UserName nullable and add ToCurrentUserResponseAsync extension; AppUserController now exposes GET /auth/user (list) and GET /auth/user/{id} (detail) using UserManager and Admin-only policy; AuthController uses the new mapper and after successful password change clears MustChangePassword, updates UpdatedAt and persists changes (with error handling) before updating security stamp. Frontend: add admin users pages (list + detail), ChangePassword page and route, adminUsers and enhanced authSession services (typed responses, changePassword API, error mapping), router guard to redirect users with mustChangePassword=true to the change-password flow, and show success banner on login after password change. UI tweaks: separate admin section in sidebar, add password-change entries in account menu, footer sizing fixes, and various layout/UX improvements. These changes enable admin account management and enforce secure password updates across the app.
123 lines
4.2 KiB
C#
123 lines
4.2 KiB
C#
using API.Contracts.Auth;
|
|
using API.Models;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace API.Controllers.Auth
|
|
{
|
|
[ApiController]
|
|
[Route("auth")]
|
|
public class AuthController(
|
|
SignInManager<AppUser> signInManager,
|
|
UserManager<AppUser> userManager)
|
|
: ControllerBase
|
|
{
|
|
[HttpPost("login")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password))
|
|
return BadRequest(new { message = "Benutzername und Passwort sind erforderlich." });
|
|
|
|
var user = await userManager.FindByNameAsync(request.UserName);
|
|
if (user is null)
|
|
return Unauthorized(new { message = "Ungültige Anmeldedaten." });
|
|
|
|
if (!user.IsActive)
|
|
return Forbid();
|
|
|
|
var result = await signInManager.PasswordSignInAsync(
|
|
user,
|
|
request.Password,
|
|
isPersistent: true,
|
|
lockoutOnFailure: false);
|
|
|
|
if (!result.Succeeded)
|
|
return Unauthorized(new { message = "Ungültige Anmeldedaten." });
|
|
|
|
user.UpdatedAt = DateTimeOffset.UtcNow;
|
|
await userManager.UpdateAsync(user);
|
|
|
|
return Ok(new { message = "Login erfolgreich." });
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
[Authorize]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
await signInManager.SignOutAsync();
|
|
return Ok(new { message = "Logout erfolgreich." });
|
|
}
|
|
|
|
[HttpGet("me")]
|
|
[Authorize]
|
|
public async Task<ActionResult<CurrentUserResponse>> Me()
|
|
{
|
|
var user = await userManager.GetUserAsync(User);
|
|
if (user is null)
|
|
return Unauthorized();
|
|
|
|
return Ok(await user.ToCurrentUserResponseAsync(userManager));
|
|
}
|
|
|
|
[HttpPost("password")]
|
|
[Authorize]
|
|
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest pwChangeDto)
|
|
{
|
|
var user = await userManager.GetUserAsync(User);
|
|
if (user is null)
|
|
return Unauthorized();
|
|
|
|
if (string.IsNullOrWhiteSpace(pwChangeDto.NewPassword) ||
|
|
string.IsNullOrWhiteSpace(pwChangeDto.OldPassword) ||
|
|
string.IsNullOrWhiteSpace(pwChangeDto.NewPasswordConfirm))
|
|
{
|
|
return BadRequest(new { message = "Alle Passwörter müssen einen Wert enthalten." });
|
|
}
|
|
|
|
if (pwChangeDto.NewPassword != pwChangeDto.NewPasswordConfirm)
|
|
{
|
|
return BadRequest(new { message = "Die neuen Passwörter stimmen nicht überein." });
|
|
}
|
|
|
|
var result = await userManager.ChangePasswordAsync(
|
|
user,
|
|
pwChangeDto.OldPassword,
|
|
pwChangeDto.NewPassword
|
|
);
|
|
|
|
if (!result.Succeeded)
|
|
{
|
|
return BadRequest(new
|
|
{
|
|
message = "Passwort konnte nicht geändert werden.",
|
|
errors = result.Errors.Select(e => e.Description)
|
|
});
|
|
}
|
|
|
|
user.MustChangePassword = false;
|
|
user.UpdatedAt = DateTimeOffset.UtcNow;
|
|
var updateResult = await userManager.UpdateAsync(user);
|
|
if (!updateResult.Succeeded)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
message = "Passwort wurde geändert, Benutzerdaten konnten aber nicht final gespeichert werden.",
|
|
errors = updateResult.Errors.Select(e => e.Description)
|
|
});
|
|
}
|
|
|
|
var stampResult = await userManager.UpdateSecurityStampAsync(user);
|
|
if (!stampResult.Succeeded)
|
|
{
|
|
return StatusCode(500, new { message = "Passwort geändert, aber Sessions konnten nicht invalidiert werden." });
|
|
}
|
|
|
|
await signInManager.SignOutAsync();
|
|
|
|
return Ok(new { message = "Passwort geändert. Du wurdest auf allen Geräten abgemeldet." });
|
|
}
|
|
}
|
|
}
|