diff --git a/src/CleanAspire.Api/IdentityApiAdditionalEndpointsExtensions.cs b/src/CleanAspire.Api/IdentityApiAdditionalEndpointsExtensions.cs index 4b7c8e1..3a8cd60 100644 --- a/src/CleanAspire.Api/IdentityApiAdditionalEndpointsExtensions.cs +++ b/src/CleanAspire.Api/IdentityApiAdditionalEndpointsExtensions.cs @@ -43,7 +43,7 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi var routeGroup = endpoints.MapGroup("/account").WithTags("Authentication", "Account Management"); routeGroup.MapPost("/logout", async (SignInManager signInManager) => { - await signInManager.SignOutAsync(); + await signInManager.SignOutAsync().ConfigureAwait(false); logger.LogInformation("User has been logged out successfully."); return Results.Ok(); }) @@ -52,9 +52,9 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi .WithDescription("Logs out the currently authenticated user by signing them out of the system. This endpoint requires the user to be authorized before calling, and returns an HTTP 200 OK response upon successful logout."); routeGroup.MapGet("/profile", async Task, ValidationProblem, NotFound>> - (ClaimsPrincipal claimsPrincipal, HttpContext context, IServiceProvider sp) => + (ClaimsPrincipal claimsPrincipal, HttpContext context) => { - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); if (await userManager.GetUserAsync(claimsPrincipal) is not { } user) { return TypedResults.NotFound(); @@ -113,9 +113,9 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi .WithDescription("Allows users to update their profile, including username, email, nickname, avatar, time zone, and language code."); routeGroup.MapPost("/updateEmail", async Task> - (ClaimsPrincipal claimsPrincipal, [FromBody] UpdateEmailRequest request, HttpContext context, [FromServices] IServiceProvider sp) => + (ClaimsPrincipal claimsPrincipal, [FromBody] UpdateEmailRequest request, HttpContext context) => { - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); if (await userManager.GetUserAsync(claimsPrincipal) is not { } user) { return TypedResults.NotFound(); @@ -143,9 +143,9 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi routeGroup.MapPost("/signup", async Task> - ([FromBody] SignupRequest request, HttpContext context, [FromServices] IServiceProvider sp) => + ([FromBody] SignupRequest request, HttpContext context) => { - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); var user = new TUser(); if (!userManager.SupportsUserEmail) { @@ -174,9 +174,9 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi .WithDescription("Allows a new user to sign up by providing required details such as email, password, and tenant-specific information. This endpoint creates a new user account and sends a confirmation email for verification."); routeGroup.MapDelete("/deleteOwnerAccount", async Task> - (ClaimsPrincipal claimsPrincipal, SignInManager signInManager, [FromBody] DeleteUserRequest request, [FromServices] IServiceProvider sp) => + (ClaimsPrincipal claimsPrincipal, SignInManager signInManager, HttpContext context,[FromBody] DeleteUserRequest request) => { - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); if (await userManager.GetUserAsync(claimsPrincipal) is not { } user) { return TypedResults.NotFound(); @@ -204,9 +204,9 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi .WithDescription("Allows users to delete their own account permanently."); routeGroup.MapGet("/confirmEmail", async Task> - ([FromQuery] string userId, [FromQuery] string code, [FromQuery] string? changedEmail, [FromServices] IServiceProvider sp) => + ([FromQuery] string userId, [FromQuery] string code, [FromQuery] string? changedEmail, HttpContext context) => { - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); if (await userManager.FindByIdAsync(userId) is not { } user) { return TypedResults.Unauthorized(); @@ -422,8 +422,8 @@ public static IEndpointRouteBuilder MapIdentityApiAdditionalEndpoints(thi }).Produces(StatusCodes.Status200OK) .ProducesValidationProblem(StatusCodes.Status422UnprocessableEntity) .ProducesProblem(StatusCodes.Status400BadRequest) - .WithSummary("") - .WithDescription(""); + .WithSummary("External Login with Google OAuth") + .WithDescription("Handles external login using Google OAuth 2.0. Exchanges an authorization code for tokens, validates the user's identity, and signs the user in."); async Task SendConfirmationEmailAsync(TUser user, UserManager userManager, HttpContext context, string email, bool isChange = false) { @@ -449,7 +449,7 @@ async Task SendConfirmationEmailAsync(TUser user, UserManager userManager await emailSender.SendConfirmationLinkAsync(user, email, HtmlEncoder.Default.Encode(confirmEmailUrl)); } routeGroup.MapPost("/forgotPassword", async Task> - (HttpContext context, [FromBody] ForgotPasswordRequest resetRequest, [FromServices] IServiceProvider sp) => + (HttpContext context, [FromBody] ForgotPasswordRequest resetRequest) => { var configuration = context.RequestServices.GetRequiredService(); var clientBaseUrl = configuration["ClientBaseUrl"]; @@ -457,7 +457,7 @@ async Task SendConfirmationEmailAsync(TUser user, UserManager userManager { throw new InvalidOperationException("Client base URL is not configured."); } - var userManager = sp.GetRequiredService>(); + var userManager = context.RequestServices.GetRequiredService>(); var user = await userManager.FindByEmailAsync(resetRequest.Email); if (user is not null && await userManager.IsEmailConfirmedAsync(user)) diff --git a/src/CleanAspire.ClientApp/Client/Account/Google/SignIn/SignInRequestBuilder.cs b/src/CleanAspire.ClientApp/Client/Account/Google/SignIn/SignInRequestBuilder.cs index 9f0917a..ed6582d 100644 --- a/src/CleanAspire.ClientApp/Client/Account/Google/SignIn/SignInRequestBuilder.cs +++ b/src/CleanAspire.ClientApp/Client/Account/Google/SignIn/SignInRequestBuilder.cs @@ -33,6 +33,9 @@ public SignInRequestBuilder(Dictionary pathParameters, IRequestA public SignInRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/account/google/signIn?code={code}&state={state}", rawUrl) { } + /// + /// Handles external login using Google OAuth 2.0. Exchanges an authorization code for tokens, validates the user's identity, and signs the user in. + /// /// A /// Cancellation token to use when cancelling requests /// Configuration for the request such as headers, query parameters, and middleware options. @@ -55,6 +58,9 @@ public async Task PostAsync(Action(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); } + /// + /// Handles external login using Google OAuth 2.0. Exchanges an authorization code for tokens, validates the user's identity, and signs the user in. + /// /// A /// Configuration for the request such as headers, query parameters, and middleware options. #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER @@ -80,10 +86,11 @@ public RequestInformation ToPostRequestInformation(Action + /// Handles external login using Google OAuth 2.0. Exchanges an authorization code for tokens, validates the user's identity, and signs the user in. + /// [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] - #pragma warning disable CS1591 public partial class SignInRequestBuilderPostQueryParameters - #pragma warning restore CS1591 { #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER #nullable enable diff --git a/src/CleanAspire.ClientApp/Pages/Account/GoogleLoginCallback.razor b/src/CleanAspire.ClientApp/Pages/Account/GoogleLoginCallback.razor index 5ab9742..7961849 100644 --- a/src/CleanAspire.ClientApp/Pages/Account/GoogleLoginCallback.razor +++ b/src/CleanAspire.ClientApp/Pages/Account/GoogleLoginCallback.razor @@ -41,7 +41,7 @@ public string? State { get; set; } public string? Error { get; set; } - protected override async Task OnInitializedAsync() + protected override void OnInitialized() { if (string.IsNullOrEmpty(AuthorizationCode)) { @@ -55,21 +55,33 @@ return; } - try - { - await SignInManagement.LoginWithGoogle(AuthorizationCode, State); - } - catch (ProblemDetails ex) - { - // Log and display API-specific problem details - Error = $"An error occurred during login: {ex.Detail ?? "Please contact support if the issue persists."}"; - } - catch (Exception ex) + + } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) { - // Catch-all for other exceptions - Error = $"Unexpected error: {ex.Message}. Please refresh the page or contact support."; + if (!string.IsNullOrEmpty(AuthorizationCode) && !string.IsNullOrEmpty(State)) + { + try + { + await SignInManagement.LoginWithGoogle(AuthorizationCode, State); + StateHasChanged(); + } + catch (ProblemDetails ex) + { + // Log and display API-specific problem details + Error = $"An error occurred during login: {ex.Detail ?? "Please contact support if the issue persists."}"; + } + catch (Exception ex) + { + // Catch-all for other exceptions + Error = $"Unexpected error: {ex.Message}. Please refresh the page or contact support."; + } + } } } + }