feat: execute full-repo remaining parity closure plan

This commit is contained in:
Joseph Doherty
2026-02-23 13:08:52 -05:00
parent cbe1fa6121
commit 2b64d762f6
75 changed files with 2325 additions and 121 deletions

View File

@@ -0,0 +1,32 @@
namespace NATS.Server.Auth;
public interface IExternalAuthClient
{
Task<ExternalAuthDecision> AuthorizeAsync(ExternalAuthRequest request, CancellationToken ct);
}
public sealed record ExternalAuthRequest(
string? Username,
string? Password,
string? Token,
string? Jwt);
public sealed record ExternalAuthDecision(
bool Allowed,
string? Identity = null,
string? Account = null,
string? Reason = null);
public sealed class ExternalAuthOptions
{
public bool Enabled { get; set; }
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(2);
public IExternalAuthClient? Client { get; set; }
}
public sealed class ProxyAuthOptions
{
public bool Enabled { get; set; }
public string UsernamePrefix { get; set; } = "proxy:";
public string? Account { get; set; }
}

View File

@@ -49,6 +49,18 @@ public sealed class AuthService
nonceRequired = true;
}
if (options.ExternalAuth is { Enabled: true, Client: not null } externalAuth)
{
authenticators.Add(new ExternalAuthCalloutAuthenticator(externalAuth.Client, externalAuth.Timeout));
authRequired = true;
}
if (options.ProxyAuth is { Enabled: true } proxyAuth)
{
authenticators.Add(new ProxyAuthenticator(proxyAuth));
authRequired = true;
}
// Priority order (matching Go): NKeys > Users > Token > SimpleUserPassword
if (options.NKeys is { Count: > 0 })

View File

@@ -0,0 +1,42 @@
namespace NATS.Server.Auth;
public sealed class ExternalAuthCalloutAuthenticator : IAuthenticator
{
private readonly IExternalAuthClient _client;
private readonly TimeSpan _timeout;
public ExternalAuthCalloutAuthenticator(IExternalAuthClient client, TimeSpan timeout)
{
_client = client;
_timeout = timeout;
}
public AuthResult? Authenticate(ClientAuthContext context)
{
using var cts = new CancellationTokenSource(_timeout);
ExternalAuthDecision decision;
try
{
decision = _client.AuthorizeAsync(
new ExternalAuthRequest(
context.Opts.Username,
context.Opts.Password,
context.Opts.Token,
context.Opts.JWT),
cts.Token).GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
return null;
}
if (!decision.Allowed)
return null;
return new AuthResult
{
Identity = decision.Identity ?? context.Opts.Username ?? "external",
AccountName = decision.Account,
};
}
}

View File

@@ -0,0 +1,27 @@
namespace NATS.Server.Auth;
public sealed class ProxyAuthenticator(ProxyAuthOptions options) : IAuthenticator
{
public AuthResult? Authenticate(ClientAuthContext context)
{
if (!options.Enabled)
return null;
var username = context.Opts.Username;
if (string.IsNullOrEmpty(username))
return null;
if (!username.StartsWith(options.UsernamePrefix, StringComparison.Ordinal))
return null;
var identity = username[options.UsernamePrefix.Length..];
if (identity.Length == 0)
return null;
return new AuthResult
{
Identity = identity,
AccountName = options.Account,
};
}
}