diff --git a/API/Contracts/Auth/CurrentUserResponse.cs b/API/Contracts/Auth/CurrentUserResponse.cs index 0e14fb3..7b49022 100644 --- a/API/Contracts/Auth/CurrentUserResponse.cs +++ b/API/Contracts/Auth/CurrentUserResponse.cs @@ -3,7 +3,7 @@ public class CurrentUserResponse { public Guid Id { get; set; } - public string UserName { get; set; } = string.Empty; + public string? UserName { get; set; } = string.Empty; public List Roles { get; set; } = new(); public bool IsActive { get; set; } public bool MustChangePassword { get; set; } diff --git a/API/Contracts/Auth/CurrentUserResponseExtensions.cs b/API/Contracts/Auth/CurrentUserResponseExtensions.cs new file mode 100644 index 0000000..e38d0d1 --- /dev/null +++ b/API/Contracts/Auth/CurrentUserResponseExtensions.cs @@ -0,0 +1,27 @@ +using API.Models; +using Microsoft.AspNetCore.Identity; + +namespace API.Contracts.Auth +{ + public static class CurrentUserResponseExtensions + { + public static async Task ToCurrentUserResponseAsync( + this AppUser user, + UserManager userManager) + { + ArgumentNullException.ThrowIfNull(user); + ArgumentNullException.ThrowIfNull(userManager); + + var roles = await userManager.GetRolesAsync(user); + + return new CurrentUserResponse + { + Id = user.Id, + UserName = user.UserName, + Roles = roles.OrderBy(role => role).ToList(), + IsActive = user.IsActive, + MustChangePassword = user.MustChangePassword, + }; + } + } +} diff --git a/API/Controllers/Auth/AppUserController.cs b/API/Controllers/Auth/AppUserController.cs index 3d18dc7..5793c17 100644 --- a/API/Controllers/Auth/AppUserController.cs +++ b/API/Controllers/Auth/AppUserController.cs @@ -1,18 +1,39 @@ +using API.Contracts.Auth; +using API.Models; using API.Security; using Microsoft.AspNetCore.Authorization; +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 : ControllerBase + public class AppUserController(UserManager userManager) : ControllerBase { [HttpGet] - [Authorize(Policy = PolicyNames.AdminOnly)] - public IActionResult GetAppUsers() + public async Task>> GetAppUsers() { - return Ok(new { message = "Adminzugriff bestätigt." }); + 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)); } } } diff --git a/API/Controllers/Auth/AuthController.cs b/API/Controllers/Auth/AuthController.cs index 696c92a..74e3f15 100644 --- a/API/Controllers/Auth/AuthController.cs +++ b/API/Controllers/Auth/AuthController.cs @@ -58,16 +58,7 @@ namespace API.Controllers.Auth if (user is null) return Unauthorized(); - var roles = await userManager.GetRolesAsync(user); - - return Ok(new CurrentUserResponse - { - Id = user.Id, - UserName = user.UserName ?? string.Empty, - Roles = roles.OrderBy(x => x).ToList(), - IsActive = user.IsActive, - MustChangePassword = user.MustChangePassword - }); + return Ok(await user.ToCurrentUserResponseAsync(userManager)); } [HttpPost("password")] @@ -105,6 +96,18 @@ namespace API.Controllers.Auth }); } + 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) { diff --git a/GUI/src/Layout.vue b/GUI/src/Layout.vue index 7e01cad..60fac58 100644 --- a/GUI/src/Layout.vue +++ b/GUI/src/Layout.vue @@ -8,6 +8,7 @@ import iconImage from '@/assets/images/icon.svg' import { Visibility, routes } from '@/plugins/routesLayout' import { AuthRequestError, + ROLE_ADMIN, fetchCurrentUser, hasRole, logout, @@ -97,8 +98,18 @@ const sidebarRoutes = computed(() => ) }), ) -const firstAuthenticatedSidebarIndex = computed(() => - sidebarRoutes.value.findIndex((item) => item.visible === Visibility.Authenticated), +const adminSidebarRoutes = computed(() => + sidebarRoutes.value.filter( + (item) => + item.visible === Visibility.Authorized && + Array.isArray(item.requiredRoles) && + item.requiredRoles.some((role) => role.trim().toLowerCase() === ROLE_ADMIN), + ), +) +const primarySidebarRoutes = computed(() => + sidebarRoutes.value.filter( + (item) => !adminSidebarRoutes.value.some((adminItem) => adminItem.path === item.path), + ), ) const footerRoutes = computed(() => routes.filter((x) => x.visible === Visibility.Footer)) @@ -290,6 +301,12 @@ watch(