using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QRRapidoApp.Models.ViewModels; using QRRapidoApp.Services; using System.Security.Claims; namespace QRRapidoApp.Controllers { public class AccountController : Controller { private readonly IUserService _userService; private readonly AdDisplayService _adDisplayService; private readonly ILogger _logger; private readonly IConfiguration _configuration; public AccountController(IUserService userService, AdDisplayService adDisplayService, ILogger logger, IConfiguration configuration) { _userService = userService; _adDisplayService = adDisplayService; _logger = logger; _configuration = configuration; } [HttpGet] public IActionResult Login(string returnUrl = "/") { _adDisplayService.SetViewBagAds(ViewBag); ViewBag.ReturnUrl = returnUrl; return View(); } [HttpGet] public IActionResult LoginGoogle(string returnUrl = "/") { var baseUrl = _configuration.GetSection("App:BaseUrl").Value; var safeReturn = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"; var properties = new AuthenticationProperties { RedirectUri = $"{baseUrl}/Account/GoogleCallback?returnUrl={Uri.EscapeDataString(safeReturn)}" }; return Challenge(properties, GoogleDefaults.AuthenticationScheme); } [HttpGet] public IActionResult LoginMicrosoft(string returnUrl = "/") { var baseUrl = _configuration.GetSection("App:BaseUrl").Value; var safeReturn = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"; var properties = new AuthenticationProperties { RedirectUri = $"{baseUrl}/Account/MicrosoftCallback?returnUrl={Uri.EscapeDataString(safeReturn)}" }; return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme); } [HttpGet] public async Task GoogleCallback(string returnUrl = "/") { var destination = await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme, returnUrl); return Redirect(destination ?? "/"); } [HttpGet] public async Task MicrosoftCallback(string returnUrl = "/") { var destination = await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme, returnUrl); return Redirect(destination ?? "/"); } private async Task HandleExternalLoginCallbackAsync(string scheme, string returnUrl = "/") { try { _adDisplayService.SetViewBagAds(ViewBag); // Validate returnUrl to prevent open redirect if (!Url.IsLocalUrl(returnUrl)) returnUrl = "/"; // Prevent redirect loop if (returnUrl.Contains("/Account/Login", StringComparison.OrdinalIgnoreCase)) returnUrl = "/"; // OAuth middleware already signed in via cookie at /signin-google or /signin-microsoft // with the provider's claims. Authenticate from that cookie to get the provider identity. var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); if (!result.Succeeded) { _logger.LogWarning("Cookie authentication failed after {Scheme} OAuth callback", scheme); return "/Account/Login"; } var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value; var name = result.Principal?.FindFirst(ClaimTypes.Name)?.Value; var providerId = result.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId)) { _logger.LogWarning($"Missing required claims from {scheme} authentication"); return "/Account/Login"; } // Find or create user var user = await _userService.GetUserByProviderAsync(scheme, providerId); bool isNewUser = user == null; if (isNewUser) { // Fix CS8625: Ensure name is not null var safeName = !string.IsNullOrEmpty(name) ? name : (email ?? "User"); user = await _userService.CreateUserAsync(email, safeName, scheme, providerId); // Auto-create first API key so user can start immediately try { var (rawKey, _) = await _userService.GenerateApiKeyAsync(user.Id, "Minha primeira key"); TempData["NewKey"] = rawKey; TempData["NewKeyName"] = "Minha primeira key"; _logger.LogInformation("Auto-created API key for new user {UserId}", user.Id); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to auto-create API key for new user {UserId}", user.Id); } } else { await _userService.UpdateLastLoginAsync(user.Id); } // Create application claims var claims = new List { new Claim(ClaimTypes.NameIdentifier, user.Id ?? string.Empty), // Fix CS8625 new Claim(ClaimTypes.Email, user.Email ?? string.Empty), // Fix CS8625 new Claim(ClaimTypes.Name, user.Name ?? string.Empty), // Fix CS8625 new Claim("Provider", user.Provider ?? string.Empty), new Claim("IsPremium", user.IsPremium.ToString()) }; var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30) }; await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return returnUrl; } catch (Exception ex) { _logger.LogError(ex, $"Error in external login callback for {scheme}"); return "/Account/Login"; } } [HttpPost] [Authorize] public async Task Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } [HttpGet] [Authorize] public async Task Profile() { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return RedirectToAction("Login"); } var user = await _userService.GetUserAsync(userId); if (user == null) { return RedirectToAction("Login"); } // Ensure we are passing a non-null userId ViewBag.QRHistory = await _userService.GetUserQRHistoryAsync(userId, 10); ViewBag.MonthlyQRCount = await _userService.GetQRCountThisMonthAsync(userId); ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId); _adDisplayService.SetViewBagAds(ViewBag); return View(user); } [HttpGet] public async Task AdFreeStatus() { var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return Json(new AdFreeStatusViewModel { IsAdFree = false, TimeRemaining = 0, IsPremium = false }); } var shouldShowAds = await _adDisplayService.ShouldShowAds(userId); var isPremium = await _adDisplayService.HasValidPremiumSubscription(userId); var expiryDate = await _adDisplayService.GetAdFreeExpiryDate(userId); var status = await _adDisplayService.GetAdFreeStatusAsync(userId); return Json(new AdFreeStatusViewModel { IsAdFree = !shouldShowAds, TimeRemaining = isPremium ? int.MaxValue : 0, IsPremium = isPremium, ExpiryDate = expiryDate, SessionType = status }); } [HttpPost] [Authorize] public async Task ExtendAdFreeTime(int minutes) { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; // Método removido - sem extensão de tempo ad-free return Json(new { success = false, message = "Feature not available" }); } [HttpGet] [Authorize] public async Task History() { _adDisplayService.SetViewBagAds(ViewBag); var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return RedirectToAction("Login"); } var history = await _userService.GetUserQRHistoryAsync(userId, 50); return View(history); } [HttpPost] [Authorize] public async Task UpdatePreferences(string language) { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return Json(new { success = false }); } try { var user = await _userService.GetUserAsync(userId); if (user != null) { user.PreferredLanguage = language; await _userService.UpdateUserAsync(user); return Json(new { success = true }); } } catch (Exception ex) { _logger.LogError(ex, $"Error updating preferences for user {userId}"); } return Json(new { success = false }); } } }