Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions/Helpers/LogThrottle.cs
T
Oliver Kieselbach dd4975b73e Auth: an external identity never claims a tenant (D-353)
A caller whose token shows it is not a member of the token's tenant (acct = 1, a #EXT# user name,
or an idp naming another tenant, a personal Microsoft account or an external sign-in provider) no
longer seeds DomainName/OnboardedBy or queues the activation, is never the first-user Tenant Admin,
and cannot set the activation-page address in the roleless signup window. Positive evidence only:
a token without these signals stays a member's, and tid/idp are read under their inbound-mapped
names as well. Activation promotes OnboardedBy only; the UpdatedBy fallback is gone and a stored
#EXT# UPN is refused. Explicit grants (TenantAdmins row, app role) still apply.

The once-per-interval log gate of the client-app binding becomes Helpers/LogThrottle and also
throttles the new warning, which the polling activation page would otherwise repeat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:19:56 +02:00

35 lines
1.2 KiB
C#

using System.Collections.Concurrent;
namespace AutopilotMonitor.Functions.Helpers;
/// <summary>
/// Lets one log line per key through per interval, for request paths that would otherwise repeat it on every
/// call (worker traces reach App Insights at Warning+ only, so a polled or hot path must not flood them).
/// Process-local and bounded: past <c>maxKeys</c> the memory is dropped, which at worst repeats a line early.
/// </summary>
public sealed class LogThrottle
{
private readonly ConcurrentDictionary<string, DateTime> _lastLogged = new();
private readonly TimeSpan _interval;
private readonly int _maxKeys;
public LogThrottle(TimeSpan interval, int maxKeys = 1000)
{
_interval = interval;
_maxKeys = maxKeys;
}
/// <summary>True when the line for <paramref name="key"/> may be written now; records that it was.</summary>
public bool ShouldLog(string key) => ShouldLog(key, DateTime.UtcNow);
internal bool ShouldLog(string key, DateTime nowUtc)
{
if (_lastLogged.TryGetValue(key, out var last) && nowUtc - last < _interval)
return false;
if (_lastLogged.Count >= _maxKeys)
_lastLogged.Clear();
_lastLogged[key] = nowUtc;
return true;
}
}