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);
}