using System.Text.Json; namespace ClaudeDo.Worker.Online; /// /// Minimal, dependency-free reader for a JWT access token's payload claims. Used to resolve the /// current user's Zitadel subject (sub) so sync payloads can be stamped with an owner. /// Never throws — returns null when the token is absent or cannot be parsed. /// public static class JwtClaims { /// /// Returns the sub claim of the JWT, or null if the token is absent/unparseable or /// carries no subject. /// public static string? GetSubject(string? jwt) { if (string.IsNullOrWhiteSpace(jwt)) return null; var parts = jwt.Split('.'); if (parts.Length < 2) return null; try { using var doc = JsonDocument.Parse(Base64UrlDecode(parts[1])); if (doc.RootElement.TryGetProperty("sub", out var sub) && sub.ValueKind == JsonValueKind.String) return sub.GetString(); return null; } catch { return null; } } private static byte[] Base64UrlDecode(string input) { var s = input.Replace('-', '+').Replace('_', '/'); switch (s.Length % 4) { case 2: s += "=="; break; case 3: s += "="; break; } return Convert.FromBase64String(s); } }