feat(pos-transactions-bff): harden session auth enforcement
This commit is contained in:
parent
dd199a5afe
commit
ff62214106
@ -23,6 +23,8 @@ docker run --rm -p 8080:8080 --name pos-transactions-bff agilewebs/pos-transacti
|
|||||||
## Runtime Notes
|
## Runtime Notes
|
||||||
|
|
||||||
- Exposes REST edge endpoints for transaction summary and payment capture.
|
- Exposes REST edge endpoints for transaction summary and payment capture.
|
||||||
|
- Requires `ThalosAuth__BaseAddress` to resolve Thalos session introspection endpoint.
|
||||||
|
- Returns standardized auth failures (`401|403|503`) with `x-correlation-id` propagation.
|
||||||
|
|
||||||
## Health Endpoint Consistency
|
## Health Endpoint Consistency
|
||||||
|
|
||||||
|
|||||||
@ -38,8 +38,13 @@ Standard auth error payload:
|
|||||||
|
|
||||||
- `401`: missing or invalid session
|
- `401`: missing or invalid session
|
||||||
- `403`: permission denied by identity service
|
- `403`: permission denied by identity service
|
||||||
|
- `503`: identity service unavailable or timeout (`identity_unavailable|identity_timeout`)
|
||||||
|
|
||||||
## Correlation
|
## Correlation
|
||||||
|
|
||||||
- Incoming/outgoing correlation header: `x-correlation-id`
|
- Incoming/outgoing correlation header: `x-correlation-id`
|
||||||
- Correlation ID is forwarded to Thalos session validation call.
|
- Correlation ID is forwarded to Thalos session validation call.
|
||||||
|
|
||||||
|
## Validation Rule
|
||||||
|
|
||||||
|
- Successful session introspection must also include `isAuthenticated=true` in Thalos response payload.
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using Microsoft.Extensions.Primitives;
|
|||||||
using Pos.Transactions.Bff.Application.Adapters;
|
using Pos.Transactions.Bff.Application.Adapters;
|
||||||
using Pos.Transactions.Bff.Application.Handlers;
|
using Pos.Transactions.Bff.Application.Handlers;
|
||||||
using Pos.Transactions.Bff.Contracts.Requests;
|
using Pos.Transactions.Bff.Contracts.Requests;
|
||||||
|
using Pos.Transactions.Bff.Rest.Security;
|
||||||
|
|
||||||
const string CorrelationHeaderName = "x-correlation-id";
|
const string CorrelationHeaderName = "x-correlation-id";
|
||||||
const string SessionAccessCookieName = "thalos_session";
|
const string SessionAccessCookieName = "thalos_session";
|
||||||
@ -109,21 +110,50 @@ async Task<IResult?> EnforceSessionAsync(
|
|||||||
request.Headers.TryAddWithoutValidation("Cookie", cookieHeader);
|
request.Headers.TryAddWithoutValidation("Cookie", cookieHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var response = await httpClientFactory.CreateClient("ThalosAuth").SendAsync(request, ct);
|
HttpResponseMessage response;
|
||||||
|
try
|
||||||
if (response.StatusCode == HttpStatusCode.Forbidden)
|
|
||||||
{
|
{
|
||||||
return ErrorResponse(StatusCodes.Status403Forbidden, "forbidden", "Permission denied.", correlationId);
|
response = await httpClientFactory.CreateClient("ThalosAuth").SendAsync(request, ct);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
return ErrorResponse(
|
||||||
|
StatusCodes.Status503ServiceUnavailable,
|
||||||
|
"identity_unavailable",
|
||||||
|
"Identity service is temporarily unavailable.",
|
||||||
|
correlationId);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return ErrorResponse(
|
||||||
|
StatusCodes.Status503ServiceUnavailable,
|
||||||
|
"identity_timeout",
|
||||||
|
"Identity service did not respond in time.",
|
||||||
|
correlationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
using (response)
|
||||||
{
|
{
|
||||||
return ErrorResponse(StatusCodes.Status401Unauthorized, "unauthorized", "Unauthorized request.", correlationId);
|
if (response.StatusCode == HttpStatusCode.Forbidden)
|
||||||
}
|
{
|
||||||
|
return ErrorResponse(StatusCodes.Status403Forbidden, "forbidden", "Permission denied.", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
return ErrorResponse(StatusCodes.Status401Unauthorized, "session_invalid", "Session validation failed.", correlationId);
|
return ErrorResponse(StatusCodes.Status401Unauthorized, "unauthorized", "Unauthorized request.", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return ErrorResponse(StatusCodes.Status401Unauthorized, "session_invalid", "Session validation failed.", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = await response.Content.ReadAsStringAsync(ct);
|
||||||
|
if (!SessionMePayloadParser.IsAuthenticated(payload))
|
||||||
|
{
|
||||||
|
return ErrorResponse(StatusCodes.Status401Unauthorized, "session_invalid", "Session validation failed.", correlationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Pos.Transactions.Bff.Rest.Security;
|
||||||
|
|
||||||
|
public static class SessionMePayloadParser
|
||||||
|
{
|
||||||
|
public static bool IsAuthenticated(string payload)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(payload))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse(payload);
|
||||||
|
if (!document.RootElement.TryGetProperty("isAuthenticated", out var value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.ValueKind == JsonValueKind.True;
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user