using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using VaultSharp; namespace DXPBLL.Auth; // NEVER log a resolved secret value — only the Vault path is safe to log. // // The JWT-signing-key method this class used to expose (GetJwtSigningKeyAsync) has moved to the // shared GB5Shared.Auth.Jwt.IJwtSigningKeyResolver (backed by GB5Shared.Vault.IVaultService), // consolidating what used to be a hand-rolled duplicate of Entitlement's ClientSecretResolver. // This class stays alive for its one remaining, JWT-unrelated responsibility: KYC field // encryption (GSTIN/PAN at rest). public class DXPSecretResolver : IDXPSecretResolver { private const string KycEncryptionKeyPath = "dxp/kyc-encryption-key"; private const string MountPoint = "secret"; private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5); private readonly IVaultClient _vault; private readonly IMemoryCache _cache; private readonly ILogger _logger; public DXPSecretResolver(IVaultClient vault, IMemoryCache cache, ILogger logger) { _vault = vault; _cache = cache; _logger = logger; } public Task GetKycEncryptionKeyAsync(CancellationToken ct) => GetSecretAsync(KycEncryptionKeyPath, ct); private async Task GetSecretAsync(string vaultKeyPath, CancellationToken ct) { if (_cache.TryGetValue(vaultKeyPath, out string? cached) && cached is not null) return cached; _logger.LogDebug("DXP: Vault cache miss for path {VaultPath} — fetching from Vault", vaultKeyPath); var secret = await _vault.V1.Secrets.KeyValue.V2 .ReadSecretAsync(path: vaultKeyPath, mountPoint: MountPoint) .ConfigureAwait(false); var value = secret?.Data?.Data?.Values.FirstOrDefault()?.ToString() ?? throw new InvalidOperationException( $"Vault secret at path '{vaultKeyPath}' returned empty or null data."); var options = new MemoryCacheEntryOptions().SetSlidingExpiration(CacheTtl); _cache.Set(vaultKeyPath, value, options); return value; } }