feat: add JWT core decode/verify and claim structs for NATS auth

Implement NatsJwt static class with Ed25519 signature verification,
base64url decoding, and JWT parsing. Add UserClaims and AccountClaims
with all NATS-specific fields (permissions, bearer tokens, limits,
signing keys, revocations). Includes 44 tests covering decode, verify,
nonce verification, and full round-trip signing with real NKey keypairs.
This commit is contained in:
Joseph Doherty
2026-02-23 04:30:20 -05:00
parent 46116400d2
commit 4836f7851e
4 changed files with 1416 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
using System.Text.Json.Serialization;
namespace NATS.Server.Auth.Jwt;
/// <summary>
/// Represents the claims in a NATS account JWT.
/// Contains standard JWT fields (sub, iss, iat, exp) and a NATS-specific nested object
/// with account limits, signing keys, and revocations.
/// </summary>
/// <remarks>
/// Reference: github.com/nats-io/jwt/v2 — AccountClaims, Account, OperatorLimits types
/// </remarks>
public sealed class AccountClaims
{
/// <summary>Subject — the account's NKey public key.</summary>
[JsonPropertyName("sub")]
public string? Subject { get; set; }
/// <summary>Issuer — the operator or signing key that issued this JWT.</summary>
[JsonPropertyName("iss")]
public string? Issuer { get; set; }
/// <summary>Issued-at time as Unix epoch seconds.</summary>
[JsonPropertyName("iat")]
public long IssuedAt { get; set; }
/// <summary>Expiration time as Unix epoch seconds. 0 means no expiry.</summary>
[JsonPropertyName("exp")]
public long Expires { get; set; }
/// <summary>Human-readable name for the account.</summary>
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>NATS-specific account claims.</summary>
[JsonPropertyName("nats")]
public AccountNats? Nats { get; set; }
}
/// <summary>
/// NATS-specific portion of account JWT claims.
/// Contains limits, signing keys, and user revocations.
/// </summary>
public sealed class AccountNats
{
/// <summary>Account resource limits.</summary>
[JsonPropertyName("limits")]
public AccountLimits? Limits { get; set; }
/// <summary>NKey public keys authorized to sign user JWTs for this account.</summary>
[JsonPropertyName("signing_keys")]
public string[]? SigningKeys { get; set; }
/// <summary>
/// Map of revoked user NKey public keys to the Unix epoch time of revocation.
/// Any user JWT issued before the revocation time is considered revoked.
/// </summary>
[JsonPropertyName("revocations")]
public Dictionary<string, long>? Revocations { get; set; }
/// <summary>Claim type (e.g., "account").</summary>
[JsonPropertyName("type")]
public string? Type { get; set; }
/// <summary>Claim version.</summary>
[JsonPropertyName("version")]
public int Version { get; set; }
}
/// <summary>
/// Resource limits for a NATS account. A value of -1 means unlimited.
/// </summary>
public sealed class AccountLimits
{
/// <summary>Maximum number of connections. -1 means unlimited.</summary>
[JsonPropertyName("conn")]
public long MaxConnections { get; set; }
/// <summary>Maximum number of subscriptions. -1 means unlimited.</summary>
[JsonPropertyName("subs")]
public long MaxSubscriptions { get; set; }
/// <summary>Maximum payload size in bytes. -1 means unlimited.</summary>
[JsonPropertyName("payload")]
public long MaxPayload { get; set; }
/// <summary>Maximum data transfer in bytes. -1 means unlimited.</summary>
[JsonPropertyName("data")]
public long MaxData { get; set; }
}