diff --git a/src/NATS.Server/Auth/ClientPermissions.cs b/src/NATS.Server/Auth/ClientPermissions.cs index c32dcd0..266cece 100644 --- a/src/NATS.Server/Auth/ClientPermissions.cs +++ b/src/NATS.Server/Auth/ClientPermissions.cs @@ -45,8 +45,18 @@ public sealed class ClientPermissions : IDisposable { if (_subscribe == null) return true; + if (!_subscribe.IsAllowed(subject)) + return false; + if (queue != null && _subscribe.IsDenied(queue)) + return false; + return true; + } - return _subscribe.IsAllowed(subject); + public bool IsDeliveryAllowed(string subject) + { + if (_subscribe == null) + return true; + return _subscribe.IsDeliveryAllowed(subject); } public void Dispose() @@ -117,6 +127,21 @@ public sealed class PermissionSet : IDisposable return allowed; } + public bool IsDenied(string subject) + { + if (_deny == null) return false; + var result = _deny.Match(subject); + return result.PlainSubs.Length > 0 || result.QueueSubs.Length > 0; + } + + public bool IsDeliveryAllowed(string subject) + { + if (_deny == null) + return true; + var result = _deny.Match(subject); + return result.PlainSubs.Length == 0 && result.QueueSubs.Length == 0; + } + public void Dispose() { _allow?.Dispose(); diff --git a/src/NATS.Server/ClientClosedReason.cs b/src/NATS.Server/ClientClosedReason.cs index 01eec58..0f3c8a1 100644 --- a/src/NATS.Server/ClientClosedReason.cs +++ b/src/NATS.Server/ClientClosedReason.cs @@ -23,6 +23,7 @@ public enum ClientClosedReason ServerShutdown, MsgHeaderViolation, NoRespondersRequiresHeaders, + AuthenticationExpired, } public static class ClientClosedReasonExtensions @@ -46,6 +47,7 @@ public static class ClientClosedReasonExtensions ClientClosedReason.ServerShutdown => "Server Shutdown", ClientClosedReason.MsgHeaderViolation => "Message Header Violation", ClientClosedReason.NoRespondersRequiresHeaders => "No Responders Requires Headers", + ClientClosedReason.AuthenticationExpired => "Authentication Expired", _ => reason.ToString(), }; } diff --git a/src/NATS.Server/NatsServer.cs b/src/NATS.Server/NatsServer.cs index 5c34f68..49b88b1 100644 --- a/src/NATS.Server/NatsServer.cs +++ b/src/NATS.Server/NatsServer.cs @@ -523,7 +523,7 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable } } - private static void DeliverMessage(Subscription sub, string subject, string? replyTo, + private void DeliverMessage(Subscription sub, string subject, string? replyTo, ReadOnlyMemory headers, ReadOnlyMemory payload) { var client = sub.Client; @@ -532,6 +532,16 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable // Check auto-unsub var count = Interlocked.Increment(ref sub.MessageCount); if (sub.MaxMessages > 0 && count > sub.MaxMessages) + { + // Clean up exhausted subscription from trie and client tracking + var subList = client.Account?.SubList ?? _globalAccount.SubList; + subList.Remove(sub); + client.RemoveSubscription(sub.Sid); + return; + } + + // Deny-list delivery filter + if (client.Permissions?.IsDeliveryAllowed(subject) == false) return; client.SendMessage(subject, sub.Sid, replyTo, headers, payload); diff --git a/src/NATS.Server/Protocol/NatsProtocol.cs b/src/NATS.Server/Protocol/NatsProtocol.cs index 7166180..70dfb3c 100644 --- a/src/NATS.Server/Protocol/NatsProtocol.cs +++ b/src/NATS.Server/Protocol/NatsProtocol.cs @@ -33,6 +33,7 @@ public static class NatsProtocol public const string ErrPermissionsSubscribe = "Permissions Violation for Subscription"; public const string ErrSlowConsumer = "Slow Consumer"; public const string ErrNoRespondersRequiresHeaders = "No Responders Requires Headers Support"; + public const string ErrMaxSubscriptionsExceeded = "Maximum Subscriptions Exceeded"; } public sealed class ServerInfo diff --git a/tests/NATS.Server.Tests/ClientClosedReasonTests.cs b/tests/NATS.Server.Tests/ClientClosedReasonTests.cs index 16edf19..b2b23c9 100644 --- a/tests/NATS.Server.Tests/ClientClosedReasonTests.cs +++ b/tests/NATS.Server.Tests/ClientClosedReasonTests.cs @@ -5,10 +5,10 @@ public class ClientClosedReasonTests [Fact] public void All_expected_close_reasons_exist() { - // Verify all 17 enum values exist and are distinct (None + 16 named reasons) + // Verify all 18 enum values exist and are distinct (None + 17 named reasons) var values = Enum.GetValues(); - values.Length.ShouldBe(17); - values.Distinct().Count().ShouldBe(17); + values.Length.ShouldBe(18); + values.Distinct().Count().ShouldBe(18); } [Theory]