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 { /// /// Controller für Authentifizierungsaktionen (Login, Logout) /// /// /// 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. /// [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 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 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); } /// /// Handles user registration via form POST. /// [HttpPost("register")] [ValidateAntiForgeryToken] public async Task 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}"); } /// /// Handles password reset request (forgot password). /// [HttpPost("forgot-password")] [ValidateAntiForgeryToken] public async Task 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"); } /// /// Handles password reset with token. /// [HttpPost("reset-password")] [ValidateAntiForgeryToken] public async Task 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}"); } } }