Files
ClaudeDo/src/ClaudeDo.Worker/DpapiTokenStore.cs
T

58 lines
1.8 KiB
C#

using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using ClaudeDo.Data;
namespace ClaudeDo.Worker;
/// <summary>
/// Persists a single secret encrypted with DPAPI (CurrentUser scope), one instance per file.
/// Windows-only. Used for the Online-Inbox refresh token (~/.claudeDo/online-inbox.token) and
/// the ticket-system PAT (~/.claudeDo/ticket.pat).
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class DpapiTokenStore
{
private readonly string _tokenPath;
public DpapiTokenStore(string tokenPath) => _tokenPath = tokenPath;
public static DpapiTokenStore InAppData(string fileName)
=> new(Path.Combine(Paths.AppDataRoot(), fileName));
public void Save(string secret)
{
ArgumentException.ThrowIfNullOrEmpty(secret);
var cipher = ProtectedData.Protect(Encoding.UTF8.GetBytes(secret), null, DataProtectionScope.CurrentUser);
Directory.CreateDirectory(Path.GetDirectoryName(_tokenPath)!);
File.WriteAllBytes(_tokenPath, cipher);
}
public string? Read()
{
if (!File.Exists(_tokenPath)) return null;
try
{
var plain = ProtectedData.Unprotect(File.ReadAllBytes(_tokenPath), null, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plain);
}
catch
{
return null;
}
}
public bool Exists() => File.Exists(_tokenPath);
public void Clear()
{
if (File.Exists(_tokenPath)) File.Delete(_tokenPath);
}
}
/// DI-Marker: der Online-Inbox-Refresh-Token.
public sealed record OnlineRefreshTokenStore(DpapiTokenStore Store);
/// DI-Marker: der Personal Access Token des Ticketsystems.
public sealed record TicketPatStore(DpapiTokenStore Store);