using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using XFEWorkflow.Abstractions;
namespace XFEWorkflow.Infrastructure.Storage;
public sealed class CredentialVault(ClientState state)
{
private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("XFEWorkflow.Credentials.v1");
public List<string> References => state.Store.Read(p => p.InstanceCredentials.Keys.Order().ToList());
public async Task SetAsync(string reference, string secret, CancellationToken ct = default)
{
if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException("凭据存储需要 Windows");
if (string.IsNullOrWhiteSpace(reference) || string.IsNullOrWhiteSpace(secret)) throw new ArgumentException("名称和凭据不能为空");
var encrypted = Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes(secret), Entropy, DataProtectionScope.CurrentUser));
state.Store.Update(p => p.InstanceCredentials[reference.Trim()] = encrypted);
await state.SaveAsync(ct);
}
public string Get(string reference)
{
if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException("凭据存储需要 Windows");
var encrypted = state.Store.Read(p => p.InstanceCredentials.GetValueOrDefault(reference)) ?? throw new KeyNotFoundException("凭据不存在:" + reference);
return Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(encrypted), Entropy, DataProtectionScope.CurrentUser));
}
public async Task RemoveAsync(string reference)
{
state.Store.Update(p => p.InstanceCredentials.Remove(reference));
await state.SaveAsync();
}
}
public sealed class SecretRedactor
{
private readonly HashSet<string> secrets = [];
private readonly object gate = new();
public void Add(string value)
{
if (value.Length == 0) return;
var readable = JsonValue.Create(value).Display();
lock (gate)
{
secrets.Add(value);
secrets.Add(JsonEncodedText.Encode(value).ToString());
secrets.Add(readable[1..^1]);
}
}
public string Redact(string value)
{
lock (gate)
foreach (var secret in secrets.OrderByDescending(x => x.Length))
value = value.Replace(secret, "***", StringComparison.Ordinal);
return value;
}
}
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using XFEWorkflow.Abstractions;
namespace XFEWorkflow.Infrastructure.Storage;
public sealed class CredentialVault(ClientState state)
{
private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("XFEWorkflow.Credentials.v1");
public List<string> References => state.Store.Read(p => p.InstanceCredentials.Keys.Order().ToList());
public async Task SetAsync(string reference, string secret, CancellationToken ct = default)
{
if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException("凭据存储需要 Windows");
if (string.IsNullOrWhiteSpace(reference) || string.IsNullOrWhiteSpace(secret)) throw new ArgumentException("名称和凭据不能为空");
var encrypted = Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes(secret), Entropy, DataProtectionScope.CurrentUser));
state.Store.Update(p => p.InstanceCredentials[reference.Trim()] = encrypted);
await state.SaveAsync(ct);
}
public string Get(string reference)
{
if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException("凭据存储需要 Windows");
var encrypted = state.Store.Read(p => p.InstanceCredentials.GetValueOrDefault(reference)) ?? throw new KeyNotFoundException("凭据不存在:" + reference);
return Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(encrypted), Entropy, DataProtectionScope.CurrentUser));
}
public async Task RemoveAsync(string reference)
{
state.Store.Update(p => p.InstanceCredentials.Remove(reference));
await state.SaveAsync();
}
}
public sealed class SecretRedactor
{
private readonly HashSet<string> secrets = [];
private readonly object gate = new();
public void Add(string value)
{
if (value.Length == 0) return;
var readable = JsonValue.Create(value).Display();
lock (gate)
{
secrets.Add(value);
secrets.Add(JsonEncodedText.Encode(value).ToString());
secrets.Add(readable[1..^1]);
}
}
public string Redact(string value)
{
lock (gate)
foreach (var secret in secrets.OrderByDescending(x => x.Length))
value = value.Replace(secret, "***", StringComparison.Ordinal);
return value;
}
}