KoogleApp/src/Koogle.Web/Controllers/AuthController.cs

135 lines
5.1 KiB
C#

using Fluxor;
using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Infrastructure.Identity;
using Koogle.Web.Store.AuthState;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace Koogle.Web.Controllers
{
/// <summary>
/// Controller für Authentifizierungsaktionen (Login, Logout)
/// </summary>
/// <remarks>
/// UserManagerService nutzt SignInManager der wiederum nicht in einer SignalR methode funktioniert.
/// Aus dem Grund werden Login/Logout Aktionen hier im MVC Controller mit einem "normalen" Post-Request abgewickelt.
/// </remarks>
[Microsoft.AspNetCore.Mvc.Route("auth")]
public class AuthController(IDispatcher dispatcher, IUserService userService) : Controller
{
private readonly IDispatcher _dispatcher = dispatcher;
private readonly IUserService _userService = userService;
[HttpPost("login")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(
[FromForm] LoginDto input)
{
var returnUrl = string.IsNullOrWhiteSpace(input.ReturnUrl) ? "/" : input.ReturnUrl;
var user = await _userService.LoginAsync(input);
if (user != null)
{
return LocalRedirect(returnUrl);
}
return LocalRedirect("/account/login?error=true");
}
[HttpPost("logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout([FromForm] string? returnUrl = null)
{
await _userService.SignOutAsync();
_dispatcher.Dispatch(new LogoutCompleteAction());
// Nach Logout per Redirect neu laden (neuer Request -> neue Auth-Session)
returnUrl ??= "/account/login";
return LocalRedirect(returnUrl);
}
/// <summary>
/// Handles user registration via form POST.
/// </summary>
[HttpPost("register")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register([FromForm] RegisterUserDto input)
{
var result = await _userService.RegisterUserAsync(input);
if (result.Succeeded)
{
// If invite token was provided, redirect to join page
if (!string.IsNullOrWhiteSpace(input.InviteToken))
{
return LocalRedirect($"/account/login?registered=true&invite={Uri.EscapeDataString(input.InviteToken)}");
}
return LocalRedirect("/account/login?registered=true");
}
// Build error string from IdentityResult
var errors = string.Join(",", result.Errors.Select(e => e.Code));
var inviteParam = !string.IsNullOrWhiteSpace(input.InviteToken)
? $"&invite={Uri.EscapeDataString(input.InviteToken)}"
: "";
return LocalRedirect($"/account/register?error={errors}{inviteParam}");
}
/// <summary>
/// Handles password reset request (forgot password).
/// </summary>
[HttpPost("forgot-password")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword([FromForm] string email)
{
// Always show success to prevent email enumeration
var token = await _userService.RequestPasswordResetAsync(email);
if (token != null)
{
// TODO: Send email with reset link
// For now, log the token for development purposes
var resetUrl = $"/account/reset-password?email={Uri.EscapeDataString(email)}&token={Uri.EscapeDataString(token)}";
Console.WriteLine($"[DEV] Password reset link: {resetUrl}");
}
return LocalRedirect("/account/forgot-password?success=true");
}
/// <summary>
/// Handles password reset with token.
/// </summary>
[HttpPost("reset-password")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetPassword([FromForm] ResetPasswordFormDto input)
{
// Validate password confirmation
if (input.NewPassword != input.ConfirmPassword)
{
return LocalRedirect($"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error=PasswordMismatch");
}
var dto = new ResetPasswordDto
{
Email = input.Email,
Token = input.Token,
NewPassword = input.NewPassword
};
var result = await _userService.ResetPasswordAsync(dto);
if (result.Succeeded)
{
return LocalRedirect("/account/reset-password?success=true");
}
var errors = string.Join(",", result.Errors.Select(e => e.Code));
return LocalRedirect($"/account/reset-password?email={Uri.EscapeDataString(input.Email)}&token={Uri.EscapeDataString(input.Token)}&error={errors}");
}
}
}