Files
Hoard/API/Controllers/Auth/AuthController.cs
T
Jonas b2984fcf1a Replace IsAdmin with role-based admin
Switch user admin handling from an AppUser boolean to ASP.NET Identity roles. Removed AppUser.IsAdmin and related configuration/model entries; added migration ReplaceIsAdminWithRoles to copy Users.IsAdmin=true into a persistent admin role and drop the IsAdmin column. CurrentUserResponse now exposes roles (string[]), AuthController returns ordered roles from UserManager, and IdentitySeedService now ensures the admin role exists and assigns/creates an initial admin user in that role. Program.cs registers an Admin-only policy (PolicyNames/RoleNames), adjusts cookie auth events to return 401/403 for API requests, and wires up authorization. Frontend updated to use roles: authSession normalizes roles, adds hasRole and ROLE_ADMIN, router and layout support meta.requiredRoles, and new Forbidden and AdminUsers pages/route are added. codexInfo.md updated to reflect the migration to role-based auth.
2026-04-20 19:57:49 +02:00

120 lines
4.0 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();
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
});
}
[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)
});
}
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." });
}
}
}