using MongoDB.Entities; using PrivaPub.Models.Statistics; using System.Collections.Concurrent; using System.Security.Cryptography; using System.Text; namespace PrivaPub.Infrastructure.Statistics { public class InteractionSalts { readonly ConcurrentDictionary _known = new(); public async Task For(DateTime day, CancellationToken token) { day = day.Date; if (_known.TryGetValue(day, out var cached)) return cached; InteractionSalt salt; if (day == DateTime.UtcNow.Date) salt = await DB.Default.UpdateAndGet() .Match(s => s.Day == day) .Modify(b => b.SetOnInsert(s => s.Key, Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)))) .Modify(b => b.SetOnInsert(s => s.ExpiresAt, day.AddHours(30))) .Option(o => o.IsUpsert = true) .ExecuteAsync(token); else salt = await DB.Default.Find().Match(s => s.Day == day).ExecuteFirstAsync(token); if (salt?.Key == default) return default; var key = Convert.FromBase64String(salt.Key); foreach (var old in _known.Keys.Where(k => k < day.AddDays(-1))) _known.TryRemove(old, out _); _known[day] = key; return key; } public static string Hash(byte[] salt, string actorUri) => salt == default || string.IsNullOrEmpty(actorUri) ? default : Convert.ToBase64String(HMACSHA256.HashData(salt, Encoding.UTF8.GetBytes(actorUri))[..12]).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } }