#nullable enable using System; using System.Collections.Concurrent; using System.Threading.Tasks; using BTCPayServer.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace BTCPayServer.Services; public class BTCPayServerSecurityStampValidator( IOptions options, SignInManager signInManager, ILoggerFactory logger, BTCPayServerSecurityStampValidator.DisabledUsers disabledUsers) : SecurityStampValidator(options, signInManager, logger) { public class DisabledUsers { ConcurrentDictionary _DisabledUsers = new ConcurrentDictionary(); public bool HasAny => !_DisabledUsers.IsEmpty; /// /// Note that you also need to invalidate the security stamp of the user /// /// public void Add(string user) { _DisabledUsers.TryAdd(user, DateTimeOffset.UtcNow); } public void Remove(string user) { _DisabledUsers.TryRemove(user, out _); } public bool Contains(string id) => _DisabledUsers.ContainsKey(id); public void Cleanup(TimeSpan validationInterval) { if (_DisabledUsers.IsEmpty) return; var now = DateTimeOffset.UtcNow; foreach (var kv in _DisabledUsers) { if (now - kv.Value > validationInterval) Remove(kv.Key); } } } public override async Task ValidateAsync(CookieValidatePrincipalContext context) { if (disabledUsers.HasAny && context.Principal.GetIdOrNull() is string id && disabledUsers.Contains(id)) { context.Properties.IssuedUtc = null; } disabledUsers.Cleanup(Options.ValidationInterval); await base.ValidateAsync(context); } }