diff --git a/docs/architecture/bff-identity-boundary.md b/docs/architecture/bff-identity-boundary.md index efe43ea..a778e74 100644 --- a/docs/architecture/bff-identity-boundary.md +++ b/docs/architecture/bff-identity-boundary.md @@ -7,6 +7,8 @@ Keep thalos-bff as an edge adapter layer that consumes thalos-service and adopte - Edge contract handling - Service client adaptation - Correlation/tracing propagation +- Single active edge protocol policy enforcement (`rest`) +- Provider metadata propagation (`InternalJwt`, `AzureAd`, `Google`) ## Prohibited - Direct DAL access diff --git a/src/Thalos.Bff.Application/Adapters/IdentityEdgeContractAdapter.cs b/src/Thalos.Bff.Application/Adapters/IdentityEdgeContractAdapter.cs index 3f38a68..22259b0 100644 --- a/src/Thalos.Bff.Application/Adapters/IdentityEdgeContractAdapter.cs +++ b/src/Thalos.Bff.Application/Adapters/IdentityEdgeContractAdapter.cs @@ -12,13 +12,21 @@ public sealed class IdentityEdgeContractAdapter : IIdentityEdgeContractAdapter /// public EvaluateIdentityPolicyRequest ToPolicyRequest(IssueTokenApiRequest request, string permissionCode) { - return new EvaluateIdentityPolicyRequest(request.SubjectId, request.TenantId, permissionCode); + return new EvaluateIdentityPolicyRequest( + request.SubjectId, + request.TenantId, + permissionCode, + request.Provider); } /// public IssueIdentityTokenRequest ToIssueTokenRequest(IssueTokenApiRequest request) { - return new IssueIdentityTokenRequest(request.SubjectId, request.TenantId); + return new IssueIdentityTokenRequest( + request.SubjectId, + request.TenantId, + request.Provider, + request.ExternalToken); } /// @@ -30,7 +38,10 @@ public sealed class IdentityEdgeContractAdapter : IIdentityEdgeContractAdapter /// public RefreshIdentitySessionRequest ToRefreshSessionRequest(RefreshSessionApiRequest request) { - return new RefreshIdentitySessionRequest(request.RefreshToken, request.CorrelationId); + return new RefreshIdentitySessionRequest( + request.RefreshToken, + request.CorrelationId, + request.Provider); } /// diff --git a/src/Thalos.Bff.Application/Adapters/IdentityEdgeGrpcContractAdapter.cs b/src/Thalos.Bff.Application/Adapters/IdentityEdgeGrpcContractAdapter.cs index c06c1ee..7f2fc4f 100644 --- a/src/Thalos.Bff.Application/Adapters/IdentityEdgeGrpcContractAdapter.cs +++ b/src/Thalos.Bff.Application/Adapters/IdentityEdgeGrpcContractAdapter.cs @@ -11,12 +11,32 @@ public sealed class IdentityEdgeGrpcContractAdapter : IIdentityEdgeGrpcContractA /// public IssueIdentityTokenGrpcContract ToGrpc(IssueTokenApiRequest request) { - return new IssueIdentityTokenGrpcContract(request.SubjectId, request.TenantId, request.CorrelationId); + return new IssueIdentityTokenGrpcContract( + request.SubjectId, + request.TenantId, + request.CorrelationId, + request.Provider.ToString(), + request.ExternalToken); } /// public IssueTokenApiRequest FromGrpc(IssueIdentityTokenGrpcContract contract) { - return new IssueTokenApiRequest(contract.SubjectId, contract.TenantId, contract.CorrelationId); + return new IssueTokenApiRequest( + contract.SubjectId, + contract.TenantId, + contract.CorrelationId, + ParseProvider(contract.Provider), + contract.ExternalToken); + } + + private static BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider ParseProvider(string provider) + { + return Enum.TryParse( + provider, + true, + out var parsedProvider) + ? parsedProvider + : BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.InternalJwt; } } diff --git a/src/Thalos.Bff.Application/Grpc/IssueIdentityTokenGrpcContract.cs b/src/Thalos.Bff.Application/Grpc/IssueIdentityTokenGrpcContract.cs index ac57ab0..e49ccc1 100644 --- a/src/Thalos.Bff.Application/Grpc/IssueIdentityTokenGrpcContract.cs +++ b/src/Thalos.Bff.Application/Grpc/IssueIdentityTokenGrpcContract.cs @@ -6,4 +6,11 @@ namespace Thalos.Bff.Application.Grpc; /// Identity subject identifier. /// Tenant identifier. /// Request correlation identifier. -public sealed record IssueIdentityTokenGrpcContract(string SubjectId, string TenantId, string CorrelationId); +/// Identity provider. +/// External provider token when applicable. +public sealed record IssueIdentityTokenGrpcContract( + string SubjectId, + string TenantId, + string CorrelationId, + string Provider = "InternalJwt", + string ExternalToken = ""); diff --git a/src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs b/src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs index 7014cc6..aea9771 100644 --- a/src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs +++ b/src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs @@ -1,3 +1,5 @@ +using BuildingBlock.Identity.Contracts.Conventions; + namespace Thalos.Bff.Contracts.Api; /// @@ -6,4 +8,11 @@ namespace Thalos.Bff.Contracts.Api; /// Identity subject identifier. /// Tenant identifier. /// Request correlation identifier. -public sealed record IssueTokenApiRequest(string SubjectId, string TenantId, string CorrelationId = ""); +/// Identity auth provider. +/// External provider token when applicable. +public sealed record IssueTokenApiRequest( + string SubjectId, + string TenantId, + string CorrelationId = "", + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt, + string ExternalToken = ""); diff --git a/src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs b/src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs index a1765d3..43980d3 100644 --- a/src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs +++ b/src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs @@ -1,3 +1,5 @@ +using BuildingBlock.Identity.Contracts.Conventions; + namespace Thalos.Bff.Contracts.Api; /// @@ -5,4 +7,8 @@ namespace Thalos.Bff.Contracts.Api; /// /// Refresh token value. /// Request correlation identifier. -public sealed record RefreshSessionApiRequest(string RefreshToken, string CorrelationId = ""); +/// Identity auth provider. +public sealed record RefreshSessionApiRequest( + string RefreshToken, + string CorrelationId = "", + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt); diff --git a/src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj b/src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj index 04a4bbc..9a5109e 100644 --- a/src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj +++ b/src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj @@ -6,5 +6,6 @@ + diff --git a/src/Thalos.Bff.Rest/Adapters/ThalosServiceGrpcClientAdapter.cs b/src/Thalos.Bff.Rest/Adapters/ThalosServiceGrpcClientAdapter.cs index 1a597db..01f3724 100644 --- a/src/Thalos.Bff.Rest/Adapters/ThalosServiceGrpcClientAdapter.cs +++ b/src/Thalos.Bff.Rest/Adapters/ThalosServiceGrpcClientAdapter.cs @@ -25,7 +25,9 @@ public sealed class ThalosServiceGrpcClientAdapter( var grpcRequest = new IssueIdentityTokenGrpcRequest { SubjectId = request.SubjectId, - TenantId = request.TenantId + TenantId = request.TenantId, + Provider = request.Provider.ToString(), + ExternalToken = request.ExternalToken }; var grpcResponse = await grpcClient.IssueIdentityTokenAsync( @@ -43,7 +45,8 @@ public sealed class ThalosServiceGrpcClientAdapter( { SubjectId = request.SubjectId, TenantId = request.TenantId, - PermissionCode = request.PermissionCode + PermissionCode = request.PermissionCode, + Provider = request.Provider.ToString() }; var grpcResponse = await grpcClient.EvaluateIdentityPolicyAsync( @@ -63,7 +66,8 @@ public sealed class ThalosServiceGrpcClientAdapter( var grpcRequest = new IssueIdentityTokenGrpcRequest { SubjectId = request.RefreshToken, - TenantId = refreshTenantId + TenantId = refreshTenantId, + Provider = request.Provider.ToString() }; var grpcResponse = await grpcClient.IssueIdentityTokenAsync( diff --git a/src/Thalos.Bff.Rest/Program.cs b/src/Thalos.Bff.Rest/Program.cs index 3944eee..0c31b59 100644 --- a/src/Thalos.Bff.Rest/Program.cs +++ b/src/Thalos.Bff.Rest/Program.cs @@ -11,6 +11,12 @@ using Thalos.Service.Grpc; const string CorrelationHeaderName = "x-correlation-id"; var builder = WebApplication.CreateBuilder(args); +var edgeProtocol = builder.Configuration["ThalosBff:EdgeProtocol"] ?? "rest"; +if (!string.Equals(edgeProtocol, "rest", StringComparison.OrdinalIgnoreCase)) +{ + throw new InvalidOperationException( + $"Thalos BFF supports one active edge protocol per deployment. Configured: '{edgeProtocol}'. Expected: 'rest'."); +} builder.Services.AddHttpContextAccessor(); builder.Services.AddHealthChecks(); diff --git a/tests/Thalos.Bff.Application.UnitTests/ContractShapeTests.cs b/tests/Thalos.Bff.Application.UnitTests/ContractShapeTests.cs index 8f3e5b4..99f4d1c 100644 --- a/tests/Thalos.Bff.Application.UnitTests/ContractShapeTests.cs +++ b/tests/Thalos.Bff.Application.UnitTests/ContractShapeTests.cs @@ -1,4 +1,5 @@ using Core.Blueprint.Common.Contracts; +using BuildingBlock.Identity.Contracts.Conventions; using Thalos.Bff.Contracts.Api; using Thalos.Bff.Contracts.Conventions; @@ -14,6 +15,7 @@ public class ContractShapeTests Assert.Equal("user-1", request.SubjectId); Assert.Equal("tenant-1", request.TenantId); Assert.Equal("corr-123", request.CorrelationId); + Assert.Equal(IdentityAuthProvider.InternalJwt, request.Provider); } [Fact]