Format ./web/backend/API/API.Core

This commit is contained in:
Aaron Po
2026-06-20 15:14:20 -04:00
parent 3d046e369c
commit 678ee21bbd
6 changed files with 57 additions and 92 deletions

View File

@@ -8,15 +8,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
Include="Microsoft.AspNetCore.OpenApi"
Version="9.0.11"
/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
Include="FluentValidation.AspNetCore"
Version="11.3.0"
/>
<PackageReference Include="MediatR" Version="12.4.1" /> <PackageReference Include="MediatR" Version="12.4.1" />
</ItemGroup> </ItemGroup>

View File

@@ -44,29 +44,19 @@ public class JwtAuthenticationHandler(
{ {
// Use the same access-token secret source as TokenService to avoid mismatched validation. // Use the same access-token secret source as TokenService to avoid mismatched validation.
string? secret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET"); string? secret = Environment.GetEnvironmentVariable("ACCESS_TOKEN_SECRET");
if (string.IsNullOrWhiteSpace(secret)) secret = configuration["Jwt:SecretKey"]; if (string.IsNullOrWhiteSpace(secret))
secret = configuration["Jwt:SecretKey"];
if (string.IsNullOrWhiteSpace(secret)) return AuthenticateResult.Fail("JWT secret is not configured"); if (string.IsNullOrWhiteSpace(secret))
return AuthenticateResult.Fail("JWT secret is not configured");
// Check if Authorization header exists // Check if Authorization header exists
if ( if (!Request.Headers.TryGetValue("Authorization", out StringValues authHeaderValue))
!Request.Headers.TryGetValue(
"Authorization",
out StringValues authHeaderValue
)
)
return AuthenticateResult.Fail("Authorization header is missing"); return AuthenticateResult.Fail("Authorization header is missing");
string authHeader = authHeaderValue.ToString(); string authHeader = authHeaderValue.ToString();
if ( if (!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
!authHeader.StartsWith( return AuthenticateResult.Fail("Invalid authorization header format");
"Bearer ",
StringComparison.OrdinalIgnoreCase
)
)
return AuthenticateResult.Fail(
"Invalid authorization header format"
);
string token = authHeader.Substring("Bearer ".Length).Trim(); string token = authHeader.Substring("Bearer ".Length).Trim();
@@ -81,9 +71,7 @@ public class JwtAuthenticationHandler(
} }
catch (Exception ex) catch (Exception ex)
{ {
return AuthenticateResult.Fail( return AuthenticateResult.Fail($"Token validation failed: {ex.Message}");
$"Token validation failed: {ex.Message}"
);
} }
} }
@@ -97,7 +85,10 @@ public class JwtAuthenticationHandler(
Response.ContentType = "application/json"; Response.ContentType = "application/json";
Response.StatusCode = 401; Response.StatusCode = 401;
ResponseBody response = new() { Message = "Unauthorized: Invalid or missing authentication token" }; ResponseBody response = new()
{
Message = "Unauthorized: Invalid or missing authentication token",
};
await Response.WriteAsJsonAsync(response); await Response.WriteAsJsonAsync(response);
} }
} }
@@ -106,6 +97,4 @@ public class JwtAuthenticationHandler(
/// Options for the <c>JWT</c> authentication scheme handled by <see cref="JwtAuthenticationHandler" />. /// Options for the <c>JWT</c> authentication scheme handled by <see cref="JwtAuthenticationHandler" />.
/// </summary> /// </summary>
/// <remarks>No additional options are defined beyond those provided by <see cref="AuthenticationSchemeOptions" />.</remarks> /// <remarks>No additional options are defined beyond those provided by <see cref="AuthenticationSchemeOptions" />.</remarks>
public class JwtAuthenticationOptions : AuthenticationSchemeOptions public class JwtAuthenticationOptions : AuthenticationSchemeOptions { }
{
}

View File

@@ -31,7 +31,7 @@ public class ProtectedController : ControllerBase
new ResponseBody<object> new ResponseBody<object>
{ {
Message = "Protected endpoint accessed successfully", Message = "Protected endpoint accessed successfully",
Payload = new { userId, username } Payload = new { userId, username },
} }
); );
} }

View File

@@ -14,8 +14,7 @@ namespace API.Core;
/// consistent JSON error responses with appropriate HTTP status codes. /// consistent JSON error responses with appropriate HTTP status codes.
/// </summary> /// </summary>
/// <param name="logger">Logger used to record unhandled exceptions before they are translated into a response.</param> /// <param name="logger">Logger used to record unhandled exceptions before they are translated into a response.</param>
public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger) : IExceptionFilter
: IExceptionFilter
{ {
/// <summary> /// <summary>
/// Logs the exception and sets <see cref="ExceptionContext.Result" /> to an appropriate error response, /// Logs the exception and sets <see cref="ExceptionContext.Result" /> to an appropriate error response,
@@ -84,10 +83,7 @@ public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
case ValidationException fluentValidationException: case ValidationException fluentValidationException:
Dictionary<string, string[]> errors = fluentValidationException Dictionary<string, string[]> errors = fluentValidationException
.Errors.GroupBy(e => e.PropertyName) .Errors.GroupBy(e => e.PropertyName)
.ToDictionary( .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
g => g.Key,
g => g.Select(e => e.ErrorMessage).ToArray()
);
context.Result = new BadRequestObjectResult( context.Result = new BadRequestObjectResult(
new { message = "Validation failed", errors } new { message = "Validation failed", errors }
@@ -96,41 +92,33 @@ public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
break; break;
case ConflictException ex: case ConflictException ex:
context.Result = new ObjectResult( context.Result = new ObjectResult(new ResponseBody { Message = ex.Message })
new ResponseBody { Message = ex.Message }
)
{ {
StatusCode = 409 StatusCode = 409,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
case NotFoundException ex: case NotFoundException ex:
context.Result = new ObjectResult( context.Result = new ObjectResult(new ResponseBody { Message = ex.Message })
new ResponseBody { Message = ex.Message }
)
{ {
StatusCode = 404 StatusCode = 404,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
case UnauthorizedException ex: case UnauthorizedException ex:
context.Result = new ObjectResult( context.Result = new ObjectResult(new ResponseBody { Message = ex.Message })
new ResponseBody { Message = ex.Message }
)
{ {
StatusCode = 401 StatusCode = 401,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
case ForbiddenException ex: case ForbiddenException ex:
context.Result = new ObjectResult( context.Result = new ObjectResult(new ResponseBody { Message = ex.Message })
new ResponseBody { Message = ex.Message }
)
{ {
StatusCode = 403 StatusCode = 403,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
@@ -140,30 +128,25 @@ public class GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
new ResponseBody { Message = "A database error occurred." } new ResponseBody { Message = "A database error occurred." }
) )
{ {
StatusCode = 503 StatusCode = 503,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
case Domain.Exceptions.ValidationException ex: case Domain.Exceptions.ValidationException ex:
context.Result = new ObjectResult( context.Result = new ObjectResult(new ResponseBody { Message = ex.Message })
new ResponseBody { Message = ex.Message }
)
{ {
StatusCode = 400 StatusCode = 400,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;
default: default:
context.Result = new ObjectResult( context.Result = new ObjectResult(
new ResponseBody new ResponseBody { Message = "An unexpected error occurred" }
{
Message = "An unexpected error occurred"
}
) )
{ {
StatusCode = 500 StatusCode = 500,
}; };
context.ExceptionHandled = true; context.ExceptionHandled = true;
break; break;

View File

@@ -17,7 +17,11 @@ using Shared.Application.Behaviors;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Global Exception Filter // Global Exception Filter
builder.Services.AddControllers(options => { options.Filters.Add<GlobalExceptionFilter>(); }) builder
.Services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
})
.AddApplicationPart(typeof(BreweryController).Assembly) .AddApplicationPart(typeof(BreweryController).Assembly)
.AddApplicationPart(typeof(UserController).Assembly) .AddApplicationPart(typeof(UserController).Assembly)
.AddApplicationPart(typeof(AuthController).Assembly); .AddApplicationPart(typeof(AuthController).Assembly);
@@ -51,14 +55,12 @@ builder.Services.AddHealthChecks();
// Configure logging for container output // Configure logging for container output
builder.Logging.ClearProviders(); builder.Logging.ClearProviders();
builder.Logging.AddConsole(); builder.Logging.AddConsole();
if (!builder.Environment.IsProduction()) builder.Logging.AddDebug(); if (!builder.Environment.IsProduction())
builder.Logging.AddDebug();
// Configure Dependency Injection ------------------------------------------------------------------------------------- // Configure Dependency Injection -------------------------------------------------------------------------------------
builder.Services.AddSingleton< builder.Services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
ISqlConnectionFactory,
DefaultSqlConnectionFactory
>();
builder.Services.AddFeaturesBreweries(); builder.Services.AddFeaturesBreweries();
builder.Services.AddFeaturesUserManagement(); builder.Services.AddFeaturesUserManagement();
@@ -75,10 +77,7 @@ builder.Services.AddScoped<GlobalExceptionFilter>();
// Configure JWT Authentication // Configure JWT Authentication
builder builder
.Services.AddAuthentication("JWT") .Services.AddAuthentication("JWT")
.AddScheme<JwtAuthenticationOptions, JwtAuthenticationHandler>( .AddScheme<JwtAuthenticationOptions, JwtAuthenticationHandler>("JWT", options => { });
"JWT",
options => { }
);
builder.Services.AddAuthorization(); builder.Services.AddAuthorization();