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