merge: integrate kitchen-ops-bff auth and web updates

This commit is contained in:
José René White Enciso 2026-03-11 12:39:20 -06:00
commit 36c7c8c6ba
4 changed files with 76 additions and 10 deletions

View File

@ -23,6 +23,8 @@ docker run --rm -p 8080:8080 --name kitchen-ops-bff agilewebs/kitchen-ops-bff:de
## Runtime Notes ## Runtime Notes
- Exposes REST edge endpoints for kitchen operations dashboards. - Exposes REST edge endpoints for kitchen operations dashboards.
- 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

View File

@ -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.

View File

@ -3,6 +3,7 @@ using Kitchen.Ops.Bff.Application.Adapters;
using Kitchen.Ops.Bff.Application.Handlers; using Kitchen.Ops.Bff.Application.Handlers;
using Kitchen.Ops.Bff.Contracts.Requests; using Kitchen.Ops.Bff.Contracts.Requests;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Kitchen.Ops.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;

View File

@ -0,0 +1,29 @@
using System.Text.Json;
namespace Kitchen.Ops.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;
}
}
}