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 userManager) : ControllerBase { [HttpGet] public async Task>> 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> 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 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 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)); } } }