using System.Collections.Generic; using System.Security.Claims; using GB5Shared.Auth.Jwt; namespace EntitlementBLL.Auth; /// /// The claims embedded in a client access token — deliberately much simpler than DXP's /// DXPAccessTokenClaims (DxpUserId/DxpPartyId/TenantId/DatabaseName/DatabaseType/LocalPartyId/ /// RoleCode): an Entitlement client user belongs to exactly one MCLIENT, so there is no /// party/tenant-link context to resolve or carry — just who they are, which client they belong /// to, and their role within it. /// public class ClientAccessTokenClaims : IJwtClaimsSource { public int ClientUserId { get; set; } public int ClientId { get; set; } /// "CLIENT_ADMIN" or "CLIENT_USER" — the literal string consumed by FastEndpoints' /// Roles() attribute, mirroring the JWT-claim-only role convention already used elsewhere in /// this codebase (see the gb-ent-admin menu-seed migration notes on GOODBOOKS_ADMIN). public string Role { get; set; } = string.Empty; /// EntitlementClientCapabilityCodes values granted to this ClientUserId via /// MENTITLEMENTCLIENTUSERROLE — meaningful only for ClientAdmin-tier callers, empty for /// ClientUser. Resolved once at login/refresh, carried on the Capabilities claim. public IReadOnlyList Capabilities { get; set; } = Array.Empty(); public IEnumerable ToClaims() => new[] { new Claim(ClientJwtClaimTypes.ClientUserId, ClientUserId.ToString()), new Claim(ClientJwtClaimTypes.ClientId, ClientId.ToString()), new Claim(ClientJwtClaimTypes.Role, Role), new Claim(ClientJwtClaimTypes.Capabilities, string.Join(",", Capabilities)) }; } /// /// The full result of a client login/refresh — both expiries here are the single source of /// truth, computed once by ClientJwtService.IssueTokenPairAsync from ClientJwtOptions. A calling /// BLL (e.g. a future ClientAuthBLL) must persist/report exactly these values and must never /// independently recompute or hardcode either lifetime — that duplication is precisely the drift /// bug found in DXP's AuthBLL.IssueTokensAsync. /// public class ClientTokenPair { public string AccessToken { get; set; } = string.Empty; public DateTime AccessTokenExpiresOn { get; set; } /// Raw refresh token — returned to the client once, never stored raw server-side. public string RefreshToken { get; set; } = string.Empty; /// SHA-256 hex hash of RefreshToken — this is the only refresh-token representation /// a caller should ever persist (MENTITLEMENTCLIENTREFRESHTOKEN.TOKENHASH). public string RefreshTokenHash { get; set; } = string.Empty; public DateTime RefreshTokenExpiresOn { get; set; } } // Issues/validates Entitlement's client-facing JWT session — the real implementation behind // gb-ent-client's currently-placeholder /lic/Auth.svc/GetClientContext. Access tokens are // short-lived JWTs; refresh tokens are opaque random strings whose SHA-256 hash is the only // thing persisted (MENTITLEMENTCLIENTREFRESHTOKEN.TOKENHASH), so a DB leak never exposes a // usable token — mirrors DXPJwtService's exact mechanics. public interface IClientJwtService { /// Issues a full access+refresh token pair in one call. Both lifetimes are read from /// ClientJwtOptions exactly once, right here, and the resulting expiries are returned on /// ClientTokenPair — never re-derived by the caller. Task IssueTokenPairAsync(ClientAccessTokenClaims claims, CancellationToken ct); (string RawToken, string TokenHash) GenerateRefreshToken(); string HashRefreshToken(string rawToken); }