diff --git a/src/WorkFlow.API/Controllers/ControllerExtensions.cs b/src/WorkFlow.API/Controllers/ControllerExtensions.cs index 480e924..45df8b2 100644 --- a/src/WorkFlow.API/Controllers/ControllerExtensions.cs +++ b/src/WorkFlow.API/Controllers/ControllerExtensions.cs @@ -1,76 +1,74 @@ -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; +using System.Security.Claims; using WorkFlow.API.Attributes; -namespace WorkFlow.API.Controllers +namespace WorkFlow.API.Controllers; + +[APIKeyAuth] +public static class ControllerExtensions { - [APIKeyAuth] - public static class ControllerExtensions + public static bool TryGetUserId(this ClaimsPrincipal user, out int id) => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier), out id); + + public static bool TryGetUsername(this ClaimsPrincipal user, out string username) { - public static bool TryGetUserId(this ClaimsPrincipal user, out int id) => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier), out id); + var value = user.FindFirstValue(ClaimTypes.Name); - public static bool TryGetUsername(this ClaimsPrincipal user, out string username) + if (value is null) { - var value = user.FindFirstValue(ClaimTypes.Name); - - if (value is null) - { - username = string.Empty; - return false; - } - else - { - username = value; - return true; - } + username = string.Empty; + return false; } - - public static bool TryGetName(this ClaimsPrincipal user, out string name) + else { - var value = user.FindFirstValue(ClaimTypes.Surname); - - if (value is null) - { - name = string.Empty; - return false; - } - else - { - name = value; - return true; - } + username = value; + return true; } + } - public static bool TryGetPrename(this ClaimsPrincipal user, out string prename) + public static bool TryGetName(this ClaimsPrincipal user, out string name) + { + var value = user.FindFirstValue(ClaimTypes.Surname); + + if (value is null) { - var value = user.FindFirstValue(ClaimTypes.GivenName); - - if (value is null) - { - prename = string.Empty; - return false; - } - else - { - prename = value; - return true; - } + name = string.Empty; + return false; } - - public static bool TryGetEmail(this ClaimsPrincipal user, out string email) + else { - var value = user.FindFirstValue(ClaimTypes.Email); + name = value; + return true; + } + } - if (value is null) - { - email = string.Empty; - return false; - } - else - { - email = value; - return true; - } + public static bool TryGetPrename(this ClaimsPrincipal user, out string prename) + { + var value = user.FindFirstValue(ClaimTypes.GivenName); + + if (value is null) + { + prename = string.Empty; + return false; + } + else + { + prename = value; + return true; + } + } + + public static bool TryGetEmail(this ClaimsPrincipal user, out string email) + { + var value = user.FindFirstValue(ClaimTypes.Email); + + if (value is null) + { + email = string.Empty; + return false; + } + else + { + email = value; + return true; } } } \ No newline at end of file diff --git a/src/WorkFlow.API/Controllers/PlaceholderAuthController.cs b/src/WorkFlow.API/Controllers/PlaceholderAuthController.cs index d0d6da9..f358b7b 100644 --- a/src/WorkFlow.API/Controllers/PlaceholderAuthController.cs +++ b/src/WorkFlow.API/Controllers/PlaceholderAuthController.cs @@ -13,7 +13,7 @@ namespace WorkFlow.API.Controllers; public class PlaceholderAuthController : ControllerBase { [HttpPost] - public IActionResult CreateTokenViaBody([FromBody] Login login) + public IActionResult CreateToken([FromBody] Login login) { throw new NotImplementedException(); } diff --git a/src/WorkFlow.API/Controllers/ProfileController.cs b/src/WorkFlow.API/Controllers/ProfileController.cs index f40c740..7891e9f 100644 --- a/src/WorkFlow.API/Controllers/ProfileController.cs +++ b/src/WorkFlow.API/Controllers/ProfileController.cs @@ -24,22 +24,14 @@ public class ProfileController : ControllerBase [HttpGet] public async Task GetAsync() - { - try + { + if (!User.TryGetUserId(out var userId)) { - if (!User.TryGetUserId(out var userId)) - { - _logger.LogError("Invalid user ID: Retrieved ID is null or not an integer."); - return Unauthorized("Failed to retrieve user identity."); - } + _logger.LogError("Invalid user ID: Retrieved ID is null or not an integer."); + return Unauthorized("Failed to retrieve user identity."); + } - var profile = await _mediator.ReadProfileAsync(userId); - return profile is null ? NotFound() : Ok(profile); - } - catch (Exception ex) - { - _logger.LogError(ex, "{Message}", ex.Message); - return StatusCode(500); - } + var profile = await _mediator.ReadProfileAsync(userId); + return profile is null ? NotFound() : Ok(profile); } } \ No newline at end of file diff --git a/src/WorkFlow.API/Controllers/UserController.cs b/src/WorkFlow.API/Controllers/UserController.cs index a1185d4..0c753c6 100644 --- a/src/WorkFlow.API/Controllers/UserController.cs +++ b/src/WorkFlow.API/Controllers/UserController.cs @@ -25,26 +25,18 @@ public class UserController : ControllerBase [HttpGet] public async Task GetAsync() { - try + if (!User.TryGetUserId(out var id)) { - if (!User.TryGetUserId(out var id)) - { - logger.LogError("Authorization failed: User ID claim not found."); - return Unauthorized("Failed to retrieve user identity."); - } + logger.LogError("Authorization failed: User ID claim not found."); + return Unauthorized("Failed to retrieve user identity."); + } - return await userService.ReadByIdAsync(id).ThenAsync( - Success: Ok, - Fail: IActionResult (msg, ntc) => - { - logger.LogNotice(ntc); - return NotFound(); - }); - } - catch (Exception ex) - { - logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message); - return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred."); - } + return await userService.ReadByIdAsync(id).ThenAsync( + Success: Ok, + Fail: IActionResult (msg, ntc) => + { + logger.LogNotice(ntc); + return NotFound(); + }); } } \ No newline at end of file diff --git a/src/WorkFlow.API/Middleware/ExceptionHandlingMiddleware.cs b/src/WorkFlow.API/Middleware/ExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..0f8ec6d --- /dev/null +++ b/src/WorkFlow.API/Middleware/ExceptionHandlingMiddleware.cs @@ -0,0 +1,84 @@ +using DigitalData.Core.Exceptions; +using System.Net; +using System.Text.Json; + +namespace WorkFlow.API.Middleware; + +//TODO: Fix and use DigitalData.Core.Exceptions.Middleware +/// +/// Middleware for handling exceptions globally in the application. +/// Captures exceptions thrown during the request pipeline execution, +/// logs them, and returns an appropriate HTTP response with a JSON error message. +/// +[Obsolete("Use DigitalData.Core.Exceptions.Middleware")] +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next middleware in the request pipeline. + /// The logger instance for logging exceptions. + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Invokes the middleware to handle the HTTP request. + /// + /// The HTTP context of the current request. + /// A task that represents the asynchronous operation. + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); // Continue down the pipeline + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex, _logger); + } + } + + /// + /// Handles exceptions by logging them and writing an appropriate JSON response. + /// + /// The HTTP context of the current request. + /// The exception that occurred. + /// The logger instance for logging the exception. + /// A task that represents the asynchronous operation. + private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger) + { + context.Response.ContentType = "application/json"; + + string message; + + switch (exception) + { + case BadRequestException badRequestEx: + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + message = badRequestEx.Message; + break; + + case NotFoundException notFoundEx: + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + message = notFoundEx.Message; + break; + + default: + logger.LogError(exception, "Unhandled exception occurred."); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + message = "An unexpected error occurred."; + break; + } + + await context.Response.WriteAsync(JsonSerializer.Serialize(new + { + message + })); + } +} diff --git a/src/WorkFlow.API/Program.cs b/src/WorkFlow.API/Program.cs index 9cad3ae..0966360 100644 --- a/src/WorkFlow.API/Program.cs +++ b/src/WorkFlow.API/Program.cs @@ -1,22 +1,22 @@ -using WorkFlow.Application; -using Microsoft.EntityFrameworkCore; -using WorkFlow.Infrastructure; +using DigitalData.Auth.Client; +using DigitalData.Core.Abstractions.Security.Extensions; using DigitalData.Core.Application; using DigitalData.UserManager.Application.DTOs.User; +using DigitalData.UserManager.DependencyInjection; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; -using WorkFlow.API.Models; +using Microsoft.OpenApi.Models; using NLog; using NLog.Web; +using WorkFlow.API; using WorkFlow.API.Extensions; using WorkFlow.API.Filters; -using Microsoft.OpenApi.Models; -using DigitalData.Auth.Client; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using WorkFlow.API; -using Microsoft.Extensions.Options; -using DigitalData.Core.Abstractions.Security.Extensions; -using DigitalData.UserManager.DependencyInjection; -using DigitalData.Core.Exceptions.Middleware; +using WorkFlow.API.Middleware; +using WorkFlow.API.Models; +using WorkFlow.Application; +using WorkFlow.Infrastructure; var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Info("Logging initialized."); @@ -57,8 +57,6 @@ try Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object) }); - builder.Services.ConfigureGlobalExceptionHandler(); - bool disableAPIKeyAuth = config.GetValue("DisableAPIKeyAuth"); if (disableAPIKeyAuth) builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions()); @@ -153,6 +151,8 @@ try lazyProvider.Factory = () => app.Services; + app.UseMiddleware(); + // Configure the HTTP request pipeline. if (app.Configuration.GetValue("EnableSwagger")) { @@ -168,8 +168,6 @@ try app.MapControllers(); - app.UseGlobalExceptionHandler(); - app.Run(); } catch (Exception ex) diff --git a/src/WorkFlow.API/WorkFlow.API.csproj b/src/WorkFlow.API/WorkFlow.API.csproj index 354e0de..90280d8 100644 --- a/src/WorkFlow.API/WorkFlow.API.csproj +++ b/src/WorkFlow.API/WorkFlow.API.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/WorkFlow.Application/DependencyInjection.cs b/src/WorkFlow.Application/DependencyInjection.cs index 80fd0c5..4764fca 100644 --- a/src/WorkFlow.Application/DependencyInjection.cs +++ b/src/WorkFlow.Application/DependencyInjection.cs @@ -4,7 +4,6 @@ namespace WorkFlow.Application; public static class DependencyInjection { - [Obsolete("Use MediatR")] public static IServiceCollection AddWorkFlowServices(this IServiceCollection services, Action? options = null) { WorkFlowServiceOptions diOptions = new();