From 5b0590f1133d5a82658e24dd58f4146218d2f5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Thu, 7 May 2026 19:25:52 +0200 Subject: [PATCH] feat(identity): add logging and descriptive error handling for Google callback (fixes #3) --- .../Pages/Account/Login.razor | 19 ++++++++++++ src/NexusReader.Web.New/Program.cs | 30 +++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/NexusReader.UI.Shared/Pages/Account/Login.razor b/src/NexusReader.UI.Shared/Pages/Account/Login.razor index 9635375..5d54751 100644 --- a/src/NexusReader.UI.Shared/Pages/Account/Login.razor +++ b/src/NexusReader.UI.Shared/Pages/Account/Login.razor @@ -90,11 +90,30 @@ @code { + [Parameter] + [SupplyParameterFromQuery(Name = "error")] + public string? ErrorCode { get; set; } + private LoginModel _loginModel = new(); private string? _errorMessage; private bool _isSubmitting; private bool _showPassword; + protected override void OnInitialized() + { + if (!string.IsNullOrEmpty(ErrorCode)) + { + _errorMessage = ErrorCode switch + { + "ExternalLoginFailed" => "Nie udało się zalogować przez Google. Spróbuj ponownie.", + "ProvisioningFailed" => "Wystąpił błąd podczas przygotowywania Twojego konta.", + "UserAlreadyExists" => "Użytkownik o tym adresie e-mail już istnieje. Zaloguj się tradycyjnie hasłem.", + "LockedOut" => "Twoje konto zostało zablokowane. Spróbuj ponownie później.", + _ => "Wystąpił nieoczekiwany błąd podczas logowania." + }; + } + } + private async Task HandleLogin() { _isSubmitting = true; diff --git a/src/NexusReader.Web.New/Program.cs b/src/NexusReader.Web.New/Program.cs index b28fd18..b335169 100644 --- a/src/NexusReader.Web.New/Program.cs +++ b/src/NexusReader.Web.New/Program.cs @@ -355,31 +355,57 @@ app.MapGet("/identity/login/google", (string? returnUrl) => app.MapGet("/identity/callback/google", async ( HttpContext context, SignInManager signInManager, - UserManager userManager) => + UserManager userManager, + ILogger logger) => { var info = await signInManager.GetExternalLoginInfoAsync(); - if (info == null) return Results.Redirect("/account/login?error=ExternalLoginFailed"); + if (info == null) + { + logger.LogWarning("External login info from Google is null."); + return Results.Redirect("/account/login?error=ExternalLoginFailed"); + } var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); if (result.Succeeded) { + logger.LogInformation("User logged in via Google: {Email}", info.Principal.FindFirstValue(ClaimTypes.Email)); return Results.Redirect("/"); } + if (result.IsLockedOut) + { + logger.LogWarning("User account locked out during Google login: {Email}", info.Principal.FindFirstValue(ClaimTypes.Email)); + return Results.Redirect("/account/login?error=LockedOut"); + } + // New user provisioning var email = info.Principal.FindFirstValue(ClaimTypes.Email); if (email != null) { var user = new NexusUser { UserName = email, Email = email, EmailConfirmed = true }; var createResult = await userManager.CreateAsync(user); + if (createResult.Succeeded) { await userManager.AddLoginAsync(user, info); await signInManager.SignInAsync(user, isPersistent: false); + logger.LogInformation("New user provisioned via Google: {Email}", email); return Results.Redirect("/"); } + + // Log specific errors + foreach (var error in createResult.Errors) + { + logger.LogError("Google provisioning failed for {Email}: {Code} - {Description}", email, error.Code, error.Description); + } + + if (createResult.Errors.Any(e => e.Code == "DuplicateEmail" || e.Code == "DuplicateUserName")) + { + return Results.Redirect("/account/login?error=UserAlreadyExists"); + } } + logger.LogError("Google provisioning failed - unknown reason for email {Email}", email); return Results.Redirect("/account/login?error=ProvisioningFailed"); });