feat(stage3): scaffold task-001 baseline
- WHY: establish Stage 3 task-001 execution baseline per repo intent - WHAT: add minimal solution/project skeleton and boundary docs - RULE: apply stage3 execution runtime and repository workflow directives
This commit is contained in:
parent
eb664ef06e
commit
9ca6b4c110
10
Thalos.Bff.slnx
Normal file
10
Thalos.Bff.slnx
Normal file
@ -0,0 +1,10 @@
|
||||
<Solution>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj" />
|
||||
<Project Path="src/Thalos.Bff.Application/Thalos.Bff.Application.csproj" />
|
||||
<Project Path="src/Thalos.Bff.Rest/Thalos.Bff.Rest.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/Thalos.Bff.Application.UnitTests/Thalos.Bff.Application.UnitTests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
16
docs/api/identity-edge-api.md
Normal file
16
docs/api/identity-edge-api.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Identity Edge API
|
||||
|
||||
## Active External Protocol
|
||||
|
||||
- REST is the active external protocol for this BFF deployment.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
- `POST /api/identity/token`
|
||||
- `POST /api/identity/session/refresh`
|
||||
|
||||
## Boundary Notes
|
||||
|
||||
- Endpoint handlers perform edge validation and permission checks.
|
||||
- Business orchestration remains in thalos-service.
|
||||
- Identity abstractions remain owned by Thalos repositories.
|
||||
27
docs/architecture/thalos-bff-flow.puml
Normal file
27
docs/architecture/thalos-bff-flow.puml
Normal file
@ -0,0 +1,27 @@
|
||||
@startuml
|
||||
skinparam packageStyle rectangle
|
||||
|
||||
package "thalos-bff" {
|
||||
class Program
|
||||
interface IIssueTokenHandler
|
||||
class IssueTokenHandler
|
||||
interface IRefreshSessionHandler
|
||||
class RefreshSessionHandler
|
||||
interface IPermissionGuard
|
||||
interface IThalosServiceClient
|
||||
|
||||
IssueTokenHandler ..|> IIssueTokenHandler
|
||||
RefreshSessionHandler ..|> IRefreshSessionHandler
|
||||
IssueTokenHandler --> IPermissionGuard
|
||||
IssueTokenHandler --> IThalosServiceClient
|
||||
RefreshSessionHandler --> IThalosServiceClient
|
||||
}
|
||||
|
||||
package "Clients" as Clients
|
||||
package "thalos-service" as ThalosService
|
||||
|
||||
Clients --> Program : REST
|
||||
Program --> IIssueTokenHandler
|
||||
Program --> IRefreshSessionHandler
|
||||
IThalosServiceClient ..> ThalosService : gRPC/internal
|
||||
@enduml
|
||||
11
docs/security/permission-enforcement-map.md
Normal file
11
docs/security/permission-enforcement-map.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Permission Enforcement Map
|
||||
|
||||
## Enforcement Points
|
||||
|
||||
- `identity.token.issue` evaluated at token issuance handler.
|
||||
- Session refresh guarded by edge session validation policy.
|
||||
|
||||
## Guardrail
|
||||
|
||||
- Permission checks happen at BFF entrypoints before downstream calls.
|
||||
- Authorization decisions are explicit and traceable at edge boundaries.
|
||||
23
src/Thalos.Bff.Application/Adapters/IThalosServiceClient.cs
Normal file
23
src/Thalos.Bff.Application/Adapters/IThalosServiceClient.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.Adapters;
|
||||
|
||||
/// <summary>
|
||||
/// Adapter boundary for downstream thalos-service calls.
|
||||
/// </summary>
|
||||
public interface IThalosServiceClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Requests token issuance from thalos-service.
|
||||
/// </summary>
|
||||
/// <param name="request">Token issuance request.</param>
|
||||
/// <returns>Token issuance response.</returns>
|
||||
Task<IssueTokenApiResponse> IssueTokenAsync(IssueTokenApiRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Requests token refresh from thalos-service.
|
||||
/// </summary>
|
||||
/// <param name="request">Session refresh request.</param>
|
||||
/// <returns>Session refresh response.</returns>
|
||||
Task<RefreshSessionApiResponse> RefreshSessionAsync(RefreshSessionApiRequest request);
|
||||
}
|
||||
16
src/Thalos.Bff.Application/Handlers/IIssueTokenHandler.cs
Normal file
16
src/Thalos.Bff.Application/Handlers/IIssueTokenHandler.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Edge handler boundary for token issuance entrypoint.
|
||||
/// </summary>
|
||||
public interface IIssueTokenHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles token issuance flow.
|
||||
/// </summary>
|
||||
/// <param name="request">Token issuance request.</param>
|
||||
/// <returns>Token issuance response.</returns>
|
||||
Task<IssueTokenApiResponse> HandleAsync(IssueTokenApiRequest request);
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Edge handler boundary for session refresh entrypoint.
|
||||
/// </summary>
|
||||
public interface IRefreshSessionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles session refresh flow.
|
||||
/// </summary>
|
||||
/// <param name="request">Session refresh request.</param>
|
||||
/// <returns>Session refresh response.</returns>
|
||||
Task<RefreshSessionApiResponse> HandleAsync(RefreshSessionApiRequest request);
|
||||
}
|
||||
23
src/Thalos.Bff.Application/Handlers/IssueTokenHandler.cs
Normal file
23
src/Thalos.Bff.Application/Handlers/IssueTokenHandler.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Thalos.Bff.Application.Adapters;
|
||||
using Thalos.Bff.Application.Security;
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Default edge handler for token issuance.
|
||||
/// </summary>
|
||||
public sealed class IssueTokenHandler(IThalosServiceClient serviceClient, IPermissionGuard permissionGuard)
|
||||
: IIssueTokenHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<IssueTokenApiResponse> HandleAsync(IssueTokenApiRequest request)
|
||||
{
|
||||
if (!permissionGuard.CanAccess("identity.token.issue"))
|
||||
{
|
||||
throw new UnauthorizedAccessException("Permission denied.");
|
||||
}
|
||||
|
||||
return serviceClient.IssueTokenAsync(request);
|
||||
}
|
||||
}
|
||||
17
src/Thalos.Bff.Application/Handlers/RefreshSessionHandler.cs
Normal file
17
src/Thalos.Bff.Application/Handlers/RefreshSessionHandler.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Thalos.Bff.Application.Adapters;
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Default edge handler for refresh session flow.
|
||||
/// </summary>
|
||||
public sealed class RefreshSessionHandler(IThalosServiceClient serviceClient)
|
||||
: IRefreshSessionHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<RefreshSessionApiResponse> HandleAsync(RefreshSessionApiRequest request)
|
||||
{
|
||||
return serviceClient.RefreshSessionAsync(request);
|
||||
}
|
||||
}
|
||||
14
src/Thalos.Bff.Application/Security/IPermissionGuard.cs
Normal file
14
src/Thalos.Bff.Application/Security/IPermissionGuard.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Thalos.Bff.Application.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Edge permission enforcement contract.
|
||||
/// </summary>
|
||||
public interface IPermissionGuard
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates whether a permission is satisfied for the current request context.
|
||||
/// </summary>
|
||||
/// <param name="permissionCode">Permission code to evaluate.</param>
|
||||
/// <returns>True when access is allowed.</returns>
|
||||
bool CanAccess(string permissionCode);
|
||||
}
|
||||
10
src/Thalos.Bff.Application/Thalos.Bff.Application.csproj
Normal file
10
src/Thalos.Bff.Application/Thalos.Bff.Application.csproj
Normal file
@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Thalos.Bff.Contracts\Thalos.Bff.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
8
src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs
Normal file
8
src/Thalos.Bff.Contracts/Api/IssueTokenApiRequest.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Thalos.Bff.Contracts.Api;
|
||||
|
||||
/// <summary>
|
||||
/// External API request for identity token issuance.
|
||||
/// </summary>
|
||||
/// <param name="SubjectId">Identity subject identifier.</param>
|
||||
/// <param name="TenantId">Tenant identifier.</param>
|
||||
public sealed record IssueTokenApiRequest(string SubjectId, string TenantId);
|
||||
8
src/Thalos.Bff.Contracts/Api/IssueTokenApiResponse.cs
Normal file
8
src/Thalos.Bff.Contracts/Api/IssueTokenApiResponse.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Thalos.Bff.Contracts.Api;
|
||||
|
||||
/// <summary>
|
||||
/// External API response for identity token issuance.
|
||||
/// </summary>
|
||||
/// <param name="AccessToken">Issued access token.</param>
|
||||
/// <param name="ExpiresInSeconds">Token expiration in seconds.</param>
|
||||
public sealed record IssueTokenApiResponse(string AccessToken, int ExpiresInSeconds);
|
||||
7
src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs
Normal file
7
src/Thalos.Bff.Contracts/Api/RefreshSessionApiRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Thalos.Bff.Contracts.Api;
|
||||
|
||||
/// <summary>
|
||||
/// External API request for refresh token session flow.
|
||||
/// </summary>
|
||||
/// <param name="RefreshToken">Refresh token value.</param>
|
||||
public sealed record RefreshSessionApiRequest(string RefreshToken);
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Thalos.Bff.Contracts.Api;
|
||||
|
||||
/// <summary>
|
||||
/// External API response for refresh token session flow.
|
||||
/// </summary>
|
||||
/// <param name="AccessToken">Refreshed access token.</param>
|
||||
/// <param name="ExpiresInSeconds">New token expiration in seconds.</param>
|
||||
public sealed record RefreshSessionApiResponse(string AccessToken, int ExpiresInSeconds);
|
||||
7
src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj
Normal file
7
src/Thalos.Bff.Contracts/Thalos.Bff.Contracts.csproj
Normal file
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
12
src/Thalos.Bff.Rest/Endpoints/EndpointConventions.cs
Normal file
12
src/Thalos.Bff.Rest/Endpoints/EndpointConventions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Thalos.Bff.Rest.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Defines endpoint conventions for the identity edge API.
|
||||
/// </summary>
|
||||
public static class EndpointConventions
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefix used by identity edge API routes.
|
||||
/// </summary>
|
||||
public const string ApiPrefix = "/api/identity";
|
||||
}
|
||||
18
src/Thalos.Bff.Rest/Program.cs
Normal file
18
src/Thalos.Bff.Rest/Program.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Stage 3 skeleton: single active external protocol for this deployment is REST.
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapPost("/api/identity/token", (IssueTokenApiRequest request) =>
|
||||
{
|
||||
return Results.Ok(new IssueTokenApiResponse("", 0));
|
||||
});
|
||||
|
||||
app.MapPost("/api/identity/session/refresh", (RefreshSessionApiRequest request) =>
|
||||
{
|
||||
return Results.Ok(new RefreshSessionApiResponse("", 0));
|
||||
});
|
||||
|
||||
app.Run();
|
||||
11
src/Thalos.Bff.Rest/Thalos.Bff.Rest.csproj
Normal file
11
src/Thalos.Bff.Rest/Thalos.Bff.Rest.csproj
Normal file
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Thalos.Bff.Application\Thalos.Bff.Application.csproj" />
|
||||
<ProjectReference Include="..\Thalos.Bff.Contracts\Thalos.Bff.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,38 @@
|
||||
using Thalos.Bff.Application.Adapters;
|
||||
using Thalos.Bff.Application.Handlers;
|
||||
using Thalos.Bff.Application.Security;
|
||||
using Thalos.Bff.Contracts.Api;
|
||||
|
||||
namespace Thalos.Bff.Application.UnitTests;
|
||||
|
||||
public class IssueTokenHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenPermissionAllowed_DelegatesToServiceClient()
|
||||
{
|
||||
var handler = new IssueTokenHandler(new FakeThalosServiceClient(), new AllowPermissionGuard());
|
||||
|
||||
var response = await handler.HandleAsync(new IssueTokenApiRequest("user-1", "tenant-1"));
|
||||
|
||||
Assert.Equal("token-xyz", response.AccessToken);
|
||||
Assert.Equal(1800, response.ExpiresInSeconds);
|
||||
}
|
||||
|
||||
private sealed class FakeThalosServiceClient : IThalosServiceClient
|
||||
{
|
||||
public Task<IssueTokenApiResponse> IssueTokenAsync(IssueTokenApiRequest request)
|
||||
{
|
||||
return Task.FromResult(new IssueTokenApiResponse("token-xyz", 1800));
|
||||
}
|
||||
|
||||
public Task<RefreshSessionApiResponse> RefreshSessionAsync(RefreshSessionApiRequest request)
|
||||
{
|
||||
return Task.FromResult(new RefreshSessionApiResponse("token-refreshed", 1800));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AllowPermissionGuard : IPermissionGuard
|
||||
{
|
||||
public bool CanAccess(string permissionCode) => true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Thalos.Bff.Application\Thalos.Bff.Application.csproj" />
|
||||
<ProjectReference Include="..\..\src\Thalos.Bff.Contracts\Thalos.Bff.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue
Block a user