Why: standardize session login/refresh/logout/me behavior for web clients behind thalos security boundaries. What: add canonical routes, cookie policy handling, grpc session calls, compatibility aliases, standardized auth errors, updated contracts, tests, and docs. Rule: keep BFF as edge adapter over service contracts and preserve identity ownership in thalos.
100 lines
3.7 KiB
C#
100 lines
3.7 KiB
C#
using Thalos.Bff.Application.Adapters;
|
|
using Thalos.Bff.Application.Handlers;
|
|
using Thalos.Bff.Application.Sessions;
|
|
using Thalos.Bff.Application.Security;
|
|
using Thalos.Bff.Contracts.Api;
|
|
using BuildingBlock.Identity.Contracts.Requests;
|
|
using BuildingBlock.Identity.Contracts.Responses;
|
|
|
|
namespace Thalos.Bff.Application.UnitTests;
|
|
|
|
public class IssueTokenHandlerTests
|
|
{
|
|
[Fact]
|
|
public async Task HandleAsync_WhenPermissionAllowed_DelegatesToServiceClient()
|
|
{
|
|
var handler = new IssueTokenHandler(
|
|
new FakeThalosServiceClient(),
|
|
new FakeIdentityEdgeContractAdapter(),
|
|
new AllowPermissionGuard());
|
|
|
|
var response = await handler.HandleAsync(new IssueTokenApiRequest("user-1", "tenant-1", "corr-123"));
|
|
|
|
Assert.Equal("token-xyz", response.AccessToken);
|
|
Assert.Equal(1800, response.ExpiresInSeconds);
|
|
}
|
|
|
|
private sealed class FakeThalosServiceClient : IThalosServiceClient
|
|
{
|
|
public Task<SessionTokensResult> StartSessionAsync(IssueIdentityTokenRequest request, string correlationId)
|
|
{
|
|
return Task.FromResult(new SessionTokensResult(
|
|
"token-xyz",
|
|
"refresh-xyz",
|
|
1800,
|
|
request.SubjectId,
|
|
request.TenantId,
|
|
request.Provider));
|
|
}
|
|
|
|
public Task<IssueIdentityTokenResponse> IssueTokenAsync(IssueIdentityTokenRequest request)
|
|
{
|
|
return Task.FromResult(new IssueIdentityTokenResponse("token-xyz", 1800));
|
|
}
|
|
|
|
public Task<EvaluateIdentityPolicyResponse> EvaluatePolicyAsync(EvaluateIdentityPolicyRequest request)
|
|
{
|
|
return Task.FromResult(new EvaluateIdentityPolicyResponse(request.SubjectId, request.PermissionCode, true));
|
|
}
|
|
|
|
public Task<RefreshIdentitySessionResponse> RefreshSessionAsync(RefreshIdentitySessionRequest request)
|
|
{
|
|
return Task.FromResult(new RefreshIdentitySessionResponse("token-refreshed", 1800));
|
|
}
|
|
|
|
public Task<SessionTokensResult> RefreshSessionTokensAsync(RefreshIdentitySessionRequest request)
|
|
{
|
|
return Task.FromResult(new SessionTokensResult(
|
|
"token-refreshed",
|
|
request.RefreshToken,
|
|
1800,
|
|
"user-1",
|
|
"tenant-1",
|
|
request.Provider));
|
|
}
|
|
}
|
|
|
|
private sealed class FakeIdentityEdgeContractAdapter : IIdentityEdgeContractAdapter
|
|
{
|
|
public EvaluateIdentityPolicyRequest ToPolicyRequest(IssueTokenApiRequest request, string permissionCode)
|
|
{
|
|
return new EvaluateIdentityPolicyRequest(request.SubjectId, request.TenantId, permissionCode);
|
|
}
|
|
|
|
public IssueIdentityTokenRequest ToIssueTokenRequest(IssueTokenApiRequest request)
|
|
{
|
|
return new IssueIdentityTokenRequest(request.SubjectId, request.TenantId);
|
|
}
|
|
|
|
public IssueTokenApiResponse ToIssueTokenApiResponse(IssueIdentityTokenResponse response)
|
|
{
|
|
return new IssueTokenApiResponse(response.Token, response.ExpiresInSeconds);
|
|
}
|
|
|
|
public RefreshIdentitySessionRequest ToRefreshSessionRequest(RefreshSessionApiRequest request)
|
|
{
|
|
return new RefreshIdentitySessionRequest(request.RefreshToken, request.CorrelationId);
|
|
}
|
|
|
|
public RefreshSessionApiResponse ToRefreshSessionApiResponse(RefreshIdentitySessionResponse response)
|
|
{
|
|
return new RefreshSessionApiResponse(response.Token, response.ExpiresInSeconds);
|
|
}
|
|
}
|
|
|
|
private sealed class AllowPermissionGuard : IPermissionGuard
|
|
{
|
|
public bool CanAccess(EvaluateIdentityPolicyResponse policyResponse) => policyResponse.IsAllowed;
|
|
}
|
|
}
|