feat: add response permission tracking for dynamic reply subject authorization
This commit is contained in:
78
src/NATS.Server/Auth/ResponseTracker.cs
Normal file
78
src/NATS.Server/Auth/ResponseTracker.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace NATS.Server.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks reply subjects that a client is temporarily allowed to publish to.
|
||||
/// Reference: Go client.go resp struct, setResponsePermissionIfNeeded.
|
||||
/// </summary>
|
||||
public sealed class ResponseTracker
|
||||
{
|
||||
private readonly int _maxMsgs; // 0 = unlimited
|
||||
private readonly TimeSpan _expires; // TimeSpan.Zero = no TTL
|
||||
private readonly Dictionary<string, (DateTime RegisteredAt, int Count)> _replies = new(StringComparer.Ordinal);
|
||||
private readonly object _lock = new();
|
||||
|
||||
public ResponseTracker(int maxMsgs, TimeSpan expires)
|
||||
{
|
||||
_maxMsgs = maxMsgs;
|
||||
_expires = expires;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { lock (_lock) return _replies.Count; }
|
||||
}
|
||||
|
||||
public void RegisterReply(string replySubject)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_replies[replySubject] = (DateTime.UtcNow, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReplyAllowed(string subject)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_replies.TryGetValue(subject, out var entry))
|
||||
return false;
|
||||
|
||||
if (_expires > TimeSpan.Zero && DateTime.UtcNow - entry.RegisteredAt > _expires)
|
||||
{
|
||||
_replies.Remove(subject);
|
||||
return false;
|
||||
}
|
||||
|
||||
var newCount = entry.Count + 1;
|
||||
if (_maxMsgs > 0 && newCount > _maxMsgs)
|
||||
{
|
||||
_replies.Remove(subject);
|
||||
return false;
|
||||
}
|
||||
|
||||
_replies[subject] = (entry.RegisteredAt, newCount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Prune()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_expires <= TimeSpan.Zero && _maxMsgs <= 0)
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var toRemove = new List<string>();
|
||||
foreach (var (key, entry) in _replies)
|
||||
{
|
||||
if (_expires > TimeSpan.Zero && now - entry.RegisteredAt > _expires)
|
||||
toRemove.Add(key);
|
||||
else if (_maxMsgs > 0 && entry.Count >= _maxMsgs)
|
||||
toRemove.Add(key);
|
||||
}
|
||||
foreach (var key in toRemove)
|
||||
_replies.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user