using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using ClaudeDo.Data; namespace ClaudeDo.Worker; /// /// 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). /// [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);