using GB5Shared.Auth.Jwt; using System.Collections.Generic; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; namespace RecruitmentBLL.Integration { /// Outcome of a DXP Party save call — same shape as DxpAuthOutcome. A failed sync /// must propagate a real error back to AgencyBLL.SaveAgency (never silently no-op), because a /// Recruitment Agency row with no valid DxpPartyId has no legal-identity backing at all. public class DxpPartyOutcome { public bool Success { get; set; } public string? Error { get; set; } public int DxpPartyId { get; set; } = -1; } /// /// Cross-host HTTP integration with DXP's real Party surface (hosted by PlatformHost, a /// different process than Recruitment's HRFinanceHost) — a thin wrapper over /// POST /DXP/Party/SaveParty, following the exact IHttpClientFactory named-client pattern /// established by RecruitmentBLL.Integration.DxpAuthIntegrationService/EcpIntegrationService. /// /// Unlike DxpAuthIntegrationService's Register/Login (plain, unauthenticated FastEndpoints /// Endpoint<,> — DXP resolves its own "system" LoginDTO internally), SaveParty is a /// DXPBaseEndpoint<,> (see DXPSL/Common/DXPBaseEndpoint.cs's doc comment: "Endpoints do /// not call AllowAnonymous(), so FastEndpoints' default auth requirement applies") — it /// requires a valid DXP-signed Bearer JWT resolved into a DXPCallerContext via /// HttpContext.User.Claims. Recruitment has no DXP user session to relay (this is a /// system-to-system call triggered by a recruiter saving an Agency, not a logged-in DXP user /// action), so this service mints its own short-lived DXP-signed token the exact same way /// RecruitmentBLL.CandidatePortal.CandidatePortalBLL.LoginAsync already does for the opposite /// direction (Recruitment issuing a token DXP/Recruitment both trust) — using DXP's own /// signing key (Vault path "dxp/jwt-signing-key") and issuer/audience /// ("DXPJwt:Issuer"/"DXPJwt:Audience", the same three values DXPSL/Program.cs's own /// AddJwtBearer validates against), carrying only a "dxp_user_id" claim set to a system /// sentinel (-1, matching DXPDAL.Common.DXPSystemContext.GetSystemLogin's own default). DXP's /// SaveParty then resolves that into a system LoginDTO via /// IDXPSystemContext.GetSystemLogin(caller.DxpUserId) — GetSystemLogin never looks the userId /// up against MDXPUSER, so no real DXP user account is required for this call to succeed, only /// a validly-signed token. Same precedent CandidatePortalBLL's own doc comment cites /// (EntitlementSL's "PartnerM2MJwtBearer" scheme: one module issues using another's key, both /// validate) — this is that pattern, reused for a system-to-system call instead of a /// browser-held session token. /// /// PartyTypeCode is caller-supplied (see AgencyBLL.DxpAgencyPartyTypeCode for why this module /// maps a placement agency to PartyTypeCode=1/Vendor — DXP's PartyTypeCode enum has no /// dedicated "Agency" value). /// /// Degrades to a clear failure result (Success=false + Error) on a non-2xx response or thrown /// exception — never throws back into AgencyBLL uncaught. /// public interface IDxpPartyIntegrationService { /// dxpPartyId=0 creates a new DXP Party (DXP's own PartyBLL.SavePartyAsync /// assigns the AutoNumber id and returns it); a nonzero value updates the existing Party's /// LegalName in place. Mirrors DXP's own SaveParty semantics exactly (isNew := DxpPartyId /// == 0). Task SavePartyAsync(int dxpPartyId, string legalName, byte partyTypeCode, CancellationToken ct); } /// Claims embedded in the short-lived system token this service mints purely to call /// DXP's own SaveParty endpoint — see IDxpPartyIntegrationService's doc comment. Deliberately /// carries nothing but the one claim DXPCallerContext.FromClaims actually reads for this call /// path (dxp_user_id); RoleCode/TenantId/etc are left at DXPCallerContext's own zero/empty /// defaults, which is fine because DXPSystemContext.GetSystemLogin only ever reads the /// DxpUserId it's passed. public class DxpSystemAccessTokenClaims : IJwtClaimsSource { public int DxpUserId { get; set; } = -1; public IEnumerable ToClaims() => new[] { new Claim("dxp_user_id", DxpUserId.ToString()) }; } }