using EntitlementBLL.Auth; using EntitlementSL.Common; using FastEndpoints; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using ErrorResponse = GB5Shared.Resource.Response.ErrorResponse; using static GB5Shared.GB5Constant.Constant; namespace EntitlementSL.Endpoints.ClientAuth; // Requires an authenticated CLIENT_ADMIN caller. Scoped to the CALLER's OWN ClientId — read from // verified JWT claims via ClientCallerContext, never from the request body (the request body has // no ClientId field at all, so this is enforced at the type level, not just at runtime). This is // the RBAC fix for DXP's wide-open equivalent (SaveParty/LinkPartyToTenant/GrantUserPartyRole — // none of which were access-controlled, per DXP's own "not access-controlled, flagged // deliberately" comments). // // Real framework-level gate as of item 9: AuthSchemes("ClientJwtBearer") + Roles(ClientAdmin) — // EntitlementSL/Program.cs now registers a named JWT bearer scheme validating tokens issued by // ClientJwtService (Vault-backed signing key, full issuer/audience/lifetime checks, RoleClaimType // mapped to ClientJwtClaimTypes.Role so Roles() actually matches CLIENT_ADMIN/CLIENT_USER claims). // The manual caller.Role/caller.ClientId re-check in ExecuteAsync below is KEPT as genuine // defense-in-depth (same posture as Feature/GetFeatureList.cs re-enforcing its PIE-only rule in // BLL rather than trusting Configure()-level attributes alone) — it also catches a // missing/malformed ClientId claim, which Roles() alone would not. public class CreateClientUser : BaseEndpoint> { private const string ClientJwtSchemeName = "ClientJwtBearer"; private readonly IClientAuthBLL _ClientAuthBLL; public CreateClientUser(IClientAuthBLL clientAuthBLL) => _ClientAuthBLL = clientAuthBLL; public override void Configure() { Post("/lic/ClientAuth.svc/CreateClientUser"); AuthSchemes(ClientJwtSchemeName); Roles(ClientRoleCodes.ClientAdmin); } public record Body(string Email, string FullName); public record Params( [property: FromHeader] string Login, [property: FromBody] Body RequestBody ); protected override string? GetCacheKey(Params req, LoginDTO loginDTO) => null; protected override async Task> ExecuteAsync(Params req, LoginDTO loginDTO, CancellationToken ct) { var caller = ClientCallerContext.FromClaims(HttpContext.User.Claims); if (caller.Role != ClientRoleCodes.ClientAdmin || caller.ClientId <= 0) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, loginDTO, statusCode: 403); var result = await _ClientAuthBLL.CreateClientUserAsync( caller.ClientId, req.RequestBody.Email, req.RequestBody.FullName, loginDTO, ct); return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse(result, CacheKeyLevel.NOT_REQUIRED, loginDTO); } }