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);
}
}