178bc8731e
Add server and UI support for creating admin users and forcing password change. API: introduce CreateUserRequest contract and add CreateNewAppUser endpoint in AppUserController; extend ChangeUserRequest with MustChangePassword and handle role assignment and detailed error responses (409/422/400). Frontend: new CreateUserDialog component, integrate it into AdminUsers list, and add createAdminUser service with CreateAdminUserError and payload handling; include mustChangePassword in update payloads and EditUserDialog. UI polish: enhanced app banner enter/leave animations in Layout.vue and add auto-dismiss timers/cleanup to appBanners store to limit and auto-remove banners.
164 lines
6.0 KiB
C#
164 lines
6.0 KiB
C#
using API.Contracts.Auth;
|
|
using API.Models;
|
|
using API.Security;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace API.Controllers.Auth
|
|
{
|
|
[ApiController]
|
|
[Authorize(Policy = PolicyNames.AdminOnly)]
|
|
[Route("auth/user")]
|
|
public class AppUserController(UserManager<AppUser> userManager) : ControllerBase
|
|
{
|
|
[HttpGet]
|
|
public async Task<ActionResult<IReadOnlyList<CurrentUserResponse>>> GetAppUsers()
|
|
{
|
|
var users = await userManager.Users
|
|
.OrderBy(x => x.UserName)
|
|
.ToListAsync();
|
|
|
|
var tasks = users.Select(user => user.ToCurrentUserResponseAsync(userManager));
|
|
return Ok(await Task.WhenAll(tasks));
|
|
}
|
|
|
|
[HttpGet("{id:guid}")]
|
|
public async Task<ActionResult<CurrentUserResponse>> GetAppUserById([FromRoute] Guid id)
|
|
{
|
|
var user = await userManager.Users.FirstOrDefaultAsync(x => x.Id == id);
|
|
if (user is null)
|
|
{
|
|
return NotFound(new { message = "Benutzer wurde nicht gefunden." });
|
|
}
|
|
|
|
return Ok(await user.ToCurrentUserResponseAsync(userManager));
|
|
}
|
|
|
|
[HttpPatch("{id:guid}")]
|
|
public async Task<IActionResult> UpdateAppUser([FromRoute] Guid id, [FromBody] ChangeUserRequest changeDto)
|
|
{
|
|
var user = await userManager.Users.FirstOrDefaultAsync(x => x.Id == id);
|
|
|
|
if (user is null)
|
|
{
|
|
return NotFound(new { message = "Benutzer wurde nicht gefunden." });
|
|
}
|
|
|
|
if (changeDto.IsActive != null)
|
|
{
|
|
if (!changeDto.IsActive.Value && await userManager.IsInRoleAsync(user, RoleNames.Admin))
|
|
{
|
|
return StatusCode(StatusCodes.Status403Forbidden,
|
|
new { message = "Adminkonten können nicht deaktiviert werden." });
|
|
}
|
|
|
|
user.IsActive = changeDto.IsActive.Value;
|
|
|
|
if (!changeDto.IsActive.Value)
|
|
{
|
|
var stampResult = await userManager.UpdateSecurityStampAsync(user);
|
|
if (!stampResult.Succeeded)
|
|
{
|
|
return StatusCode(500, new { message = "Benutzer wurde deaktiviert, aber Sessions konnten nicht invalidiert werden. " +
|
|
"Er könnte also immer noch Angemeldet sein!" });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changeDto.UserName != null)
|
|
{
|
|
var newUserName = changeDto.UserName.Trim();
|
|
|
|
if (string.IsNullOrEmpty(newUserName))
|
|
{
|
|
return BadRequest(new { message = "Benutzername darf nicht leer sein." });
|
|
}
|
|
|
|
if (!string.Equals(newUserName, user.UserName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var setNameResult = await userManager.SetUserNameAsync(user, newUserName);
|
|
if (!setNameResult.Succeeded)
|
|
{
|
|
if (setNameResult.Errors.Any(e => e.Code == nameof(IdentityErrorDescriber.DuplicateUserName)))
|
|
{
|
|
return Conflict(new { message = "Benutzername ist bereits vergeben." });
|
|
}
|
|
|
|
return BadRequest(new { message = "Benutzername konnte nicht geändert werden.", errors = setNameResult.Errors.Select(e => e.Description) });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changeDto.MustChangePassword != null)
|
|
{
|
|
user.MustChangePassword = changeDto.MustChangePassword.Value;
|
|
|
|
await userManager.UpdateAsync(user);
|
|
}
|
|
|
|
return Ok(await user.ToCurrentUserResponseAsync(userManager));
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> CreateNewAppUser([FromBody] CreateUserRequest createDto)
|
|
{
|
|
var newUser = new AppUser
|
|
{
|
|
UserName = createDto.UserName.Trim(),
|
|
MustChangePassword = true,
|
|
IsActive = createDto.IsActive,
|
|
};
|
|
|
|
var result = await userManager.CreateAsync(newUser, createDto.StartPassword);
|
|
|
|
if (!result.Succeeded)
|
|
{
|
|
if (result.Errors.Any(e => e.Code == nameof(IdentityErrorDescriber.DuplicateUserName)))
|
|
{
|
|
return Conflict(new { message = "Benutzername ist bereits vergeben." });
|
|
}
|
|
|
|
var passwordErrors = result.Errors
|
|
.Where(e => e.Code.StartsWith("Password"))
|
|
.Select(e => e.Description)
|
|
.ToList();
|
|
|
|
if (passwordErrors.Any())
|
|
{
|
|
return UnprocessableEntity(new
|
|
{
|
|
message = "Passwort erfüllt nicht die Sicherheitsanforderungen.",
|
|
errors = passwordErrors
|
|
});
|
|
}
|
|
|
|
return BadRequest(new
|
|
{
|
|
message = "Benutzer konnte nicht erstellt werden.",
|
|
errors = result.Errors.Select(e => e.Description)
|
|
});
|
|
}
|
|
|
|
if (createDto.IsAdmin)
|
|
{
|
|
var roleResult = await userManager.AddToRoleAsync(newUser, RoleNames.Admin);
|
|
|
|
if (!roleResult.Succeeded)
|
|
{
|
|
return BadRequest(new
|
|
{
|
|
message = "Benutzer wurde erstellt, aber Rolle konnte nicht zugewiesen werden.",
|
|
errors = roleResult.Errors.Select(e => e.Description)
|
|
});
|
|
}
|
|
}
|
|
|
|
return CreatedAtAction(nameof(GetAppUserById), new { id = newUser.Id },
|
|
await newUser.ToCurrentUserResponseAsync(userManager));
|
|
}
|
|
}
|
|
}
|