44 lines
1.4 KiB
C#
44 lines
1.4 KiB
C#
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<DateTime, byte[]> _known = new();
|
||
|
|
|
||
|
|
public async Task<byte[]> 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<InteractionSalt>()
|
||
|
|
.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<InteractionSalt>().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('/', '_');
|
||
|
|
}
|
||
|
|
}
|