fix: authcontroller
All checks were successful
Deploy QR Rapido / test (push) Successful in 54s
Deploy QR Rapido / build-and-push (push) Successful in 7m1s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 1m36s

This commit is contained in:
Ricardo Carneiro 2025-08-25 00:02:40 -03:00
parent b8ab7948a9
commit c59e6711c0

View File

@ -7,6 +7,9 @@ using Microsoft.AspNetCore.Mvc;
using QRRapidoApp.Models.ViewModels;
using QRRapidoApp.Services;
using System.Security.Claims;
using System.Text.Json;
using System.Text;
using Microsoft.AspNetCore.DataProtection;
namespace QRRapidoApp.Controllers
{
@ -16,14 +19,17 @@ namespace QRRapidoApp.Controllers
private readonly AdDisplayService _adDisplayService;
private readonly ILogger<AccountController> _logger;
private readonly IConfiguration _configuration;
private readonly IDataProtector _protector;
public AccountController(IUserService userService, AdDisplayService adDisplayService,
ILogger<AccountController> logger, IConfiguration configuration)
ILogger<AccountController> logger, IConfiguration configuration,
IDataProtectionProvider dataProtection)
{
_userService = userService;
_adDisplayService = adDisplayService;
_logger = logger;
_configuration = configuration;
_protector = dataProtection.CreateProtector("OAuth.StateProtection");
}
[HttpGet]
@ -38,11 +44,25 @@ namespace QRRapidoApp.Controllers
public IActionResult LoginGoogle(string returnUrl = "/")
{
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
// Criar state com dados criptografados em vez de sessão
var stateData = new OAuthStateData
{
ReturnUrl = returnUrl,
Nonce = Guid.NewGuid().ToString(),
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
var stateJson = JsonSerializer.Serialize(stateData);
var protectedState = _protector.Protect(stateJson);
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
var properties = new AuthenticationProperties
{
RedirectUri = $"{baseUrl}{Url.Action("GoogleCallback")}",
Items = { { "returnUrl", returnUrl } }
Items = { { "state", encodedState } }
};
return Challenge(properties, GoogleDefaults.AuthenticationScheme);
}
@ -50,40 +70,84 @@ namespace QRRapidoApp.Controllers
public IActionResult LoginMicrosoft(string returnUrl = "/")
{
var baseUrl = _configuration.GetSection("App:BaseUrl").Value;
//var redirectUrl = Url.Action("MicrosoftCallback", "Account", new { returnUrl });
var redirectUrl = "";
if (returnUrl == "/")
// Mesmo processo para Microsoft
var stateData = new OAuthStateData
{
redirectUrl = $"{baseUrl}/Account/MicrosoftCallback";
}
ReturnUrl = returnUrl,
Nonce = Guid.NewGuid().ToString(),
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
var stateJson = JsonSerializer.Serialize(stateData);
var protectedState = _protector.Protect(stateJson);
var encodedState = Convert.ToBase64String(Encoding.UTF8.GetBytes(protectedState));
var redirectUrl = returnUrl == "/"
? $"{baseUrl}/Account/MicrosoftCallback"
: $"{baseUrl}/Account/MicrosoftCallback";
var properties = new AuthenticationProperties
{
RedirectUri = redirectUrl,
Items = { { "state", encodedState } }
};
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
return Challenge(properties, MicrosoftAccountDefaults.AuthenticationScheme);
}
[HttpGet]
public async Task<IActionResult> GoogleCallback()
public async Task<IActionResult> GoogleCallback(string state = null)
{
return await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme);
var returnUrl = await HandleExternalLoginCallbackAsync(GoogleDefaults.AuthenticationScheme, state);
return Redirect(returnUrl ?? "/");
}
[HttpGet]
public async Task<IActionResult> MicrosoftCallback()
public async Task<IActionResult> MicrosoftCallback(string state = null)
{
return await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme);
var returnUrl = await HandleExternalLoginCallbackAsync(MicrosoftAccountDefaults.AuthenticationScheme, state);
return Redirect(returnUrl ?? "/");
}
private async Task<IActionResult> HandleExternalLoginCallbackAsync(string scheme)
private async Task<string> HandleExternalLoginCallbackAsync(string scheme, string state = null)
{
try
{
_adDisplayService.SetViewBagAds(ViewBag);
// Recuperar returnUrl do state em vez da sessão
string returnUrl = "/";
if (!string.IsNullOrEmpty(state))
{
try
{
var decodedState = Encoding.UTF8.GetString(Convert.FromBase64String(state));
var unprotectedState = _protector.Unprotect(decodedState);
var stateData = JsonSerializer.Deserialize<OAuthStateData>(unprotectedState);
// Validar timestamp (não mais que 10 minutos)
var maxAge = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - (10 * 60);
if (stateData.Timestamp > maxAge)
{
returnUrl = stateData.ReturnUrl ?? "/";
}
else
{
_logger.LogWarning($"OAuth state expired for scheme {scheme}");
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to decode OAuth state for scheme {scheme}");
}
}
var result = await HttpContext.AuthenticateAsync(scheme);
if (!result.Succeeded)
{
_logger.LogWarning($"External authentication failed for scheme {scheme}");
return RedirectToAction("Login");
return "/Account/Login";
}
var email = result.Principal?.FindFirst(ClaimTypes.Email)?.Value;
@ -93,7 +157,7 @@ namespace QRRapidoApp.Controllers
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(providerId))
{
_logger.LogWarning($"Missing required claims from {scheme} authentication");
return RedirectToAction("Login");
return "/Account/Login";
}
// Find or create user
@ -127,13 +191,12 @@ namespace QRRapidoApp.Controllers
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity), authProperties);
var returnUrl = result.Properties?.Items != null && result.Properties.Items.ContainsKey("returnUrl") ? result.Properties?.Items["returnUrl"] : "/";
return RedirectToAction("Index", "Home");
return returnUrl;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error in external login callback for {scheme}");
return RedirectToAction("Login");
return "/Account/Login";
}
}
@ -252,4 +315,12 @@ namespace QRRapidoApp.Controllers
return Json(new { success = false });
}
}
// Classe para dados do state
public class OAuthStateData
{
public string ReturnUrl { get; set; } = "/";
public string Nonce { get; set; } = "";
public long Timestamp { get; set; }
}
}