Fix AutoDetect
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronosAuthService.Services.Discovery;
|
||||
|
||||
public interface IDiscoveryPresenceStore : IDisposable
|
||||
{
|
||||
void Publish(string uid, IEnumerable<string> hashes, string? displayName = null);
|
||||
(bool Found, string Token, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash);
|
||||
bool ValidateToken(string token, out string targetUid);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronosAuthService.Services.Discovery;
|
||||
|
||||
public sealed class InMemoryPresenceStore : IDiscoveryPresenceStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, (string Uid, DateTimeOffset ExpiresAt, string? DisplayName)> _presence = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, (string TargetUid, DateTimeOffset ExpiresAt)> _tokens = new(StringComparer.Ordinal);
|
||||
private readonly TimeSpan _presenceTtl;
|
||||
private readonly TimeSpan _tokenTtl;
|
||||
private readonly Timer _cleanupTimer;
|
||||
|
||||
public InMemoryPresenceStore(TimeSpan presenceTtl, TimeSpan tokenTtl)
|
||||
{
|
||||
_presenceTtl = presenceTtl;
|
||||
_tokenTtl = tokenTtl;
|
||||
_cleanupTimer = new Timer(_ => Cleanup(), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cleanupTimer.Dispose();
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
foreach (var kv in _presence.ToArray())
|
||||
{
|
||||
if (kv.Value.ExpiresAt <= now) _presence.TryRemove(kv.Key, out _);
|
||||
}
|
||||
foreach (var kv in _tokens.ToArray())
|
||||
{
|
||||
if (kv.Value.ExpiresAt <= now) _tokens.TryRemove(kv.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null)
|
||||
{
|
||||
var exp = DateTimeOffset.UtcNow.Add(_presenceTtl);
|
||||
foreach (var h in hashes.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
_presence[h] = (uid, exp, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool Found, string Token, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||
{
|
||||
if (_presence.TryGetValue(hash, out var entry))
|
||||
{
|
||||
if (string.Equals(entry.Uid, requesterUid, StringComparison.Ordinal)) return (false, string.Empty, null);
|
||||
var token = Guid.NewGuid().ToString("N");
|
||||
_tokens[token] = (entry.Uid, DateTimeOffset.UtcNow.Add(_tokenTtl));
|
||||
return (true, token, entry.DisplayName);
|
||||
}
|
||||
return (false, string.Empty, null);
|
||||
}
|
||||
|
||||
public bool ValidateToken(string token, out string targetUid)
|
||||
{
|
||||
targetUid = string.Empty;
|
||||
if (_tokens.TryGetValue(token, out var info))
|
||||
{
|
||||
if (info.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
{
|
||||
targetUid = info.TargetUid;
|
||||
return true;
|
||||
}
|
||||
_tokens.TryRemove(token, out _);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace MareSynchronosAuthService.Services.Discovery;
|
||||
|
||||
public sealed class RedisPresenceStore : IDiscoveryPresenceStore
|
||||
{
|
||||
private readonly ILogger<RedisPresenceStore> _logger;
|
||||
private readonly IDatabase _db;
|
||||
private readonly TimeSpan _presenceTtl;
|
||||
private readonly TimeSpan _tokenTtl;
|
||||
private readonly JsonSerializerOptions _jsonOpts = new() { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
|
||||
|
||||
public RedisPresenceStore(ILogger<RedisPresenceStore> logger, IConnectionMultiplexer mux, TimeSpan presenceTtl, TimeSpan tokenTtl)
|
||||
{
|
||||
_logger = logger;
|
||||
_db = mux.GetDatabase();
|
||||
_presenceTtl = presenceTtl;
|
||||
_tokenTtl = tokenTtl;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
private static string KeyForHash(string hash) => $"nd:hash:{hash}";
|
||||
private static string KeyForToken(string token) => $"nd:token:{token}";
|
||||
|
||||
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null)
|
||||
{
|
||||
var entries = hashes.Distinct(StringComparer.Ordinal).ToArray();
|
||||
if (entries.Length == 0) return;
|
||||
var batch = _db.CreateBatch();
|
||||
foreach (var h in entries)
|
||||
{
|
||||
var key = KeyForHash(h);
|
||||
var payload = JsonSerializer.Serialize(new Presence(uid, displayName), _jsonOpts);
|
||||
batch.StringSetAsync(key, payload, _presenceTtl);
|
||||
}
|
||||
batch.Execute();
|
||||
_logger.LogDebug("RedisPresenceStore: published {count} hashes", entries.Length);
|
||||
}
|
||||
|
||||
public (bool Found, string Token, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||
{
|
||||
var key = KeyForHash(hash);
|
||||
var val = _db.StringGet(key);
|
||||
if (!val.HasValue) return (false, string.Empty, null);
|
||||
try
|
||||
{
|
||||
var p = JsonSerializer.Deserialize<Presence>(val!);
|
||||
if (p == null || string.IsNullOrEmpty(p.Uid)) return (false, string.Empty, null);
|
||||
if (string.Equals(p.Uid, requesterUid, StringComparison.Ordinal)) return (false, string.Empty, null);
|
||||
var token = Guid.NewGuid().ToString("N");
|
||||
_db.StringSet(KeyForToken(token), p.Uid, _tokenTtl);
|
||||
return (true, token, p.DisplayName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (false, string.Empty, null);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateToken(string token, out string targetUid)
|
||||
{
|
||||
targetUid = string.Empty;
|
||||
var key = KeyForToken(token);
|
||||
var val = _db.StringGet(key);
|
||||
if (!val.HasValue) return false;
|
||||
targetUid = val!;
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed record Presence(string Uid, string? DisplayName);
|
||||
}
|
||||
|
||||
@@ -1,88 +1,50 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MareSynchronosAuthService.Services.Discovery;
|
||||
|
||||
namespace MareSynchronosAuthService.Services;
|
||||
|
||||
public class DiscoveryPresenceService : IHostedService
|
||||
public class DiscoveryPresenceService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly ILogger<DiscoveryPresenceService> _logger;
|
||||
|
||||
// hash -> (uid, expiresAt, displayName?)
|
||||
private readonly ConcurrentDictionary<string, (string Uid, DateTimeOffset ExpiresAt, string? DisplayName)> _presence = new(StringComparer.Ordinal);
|
||||
|
||||
// token -> (targetUid, expiresAt)
|
||||
private readonly ConcurrentDictionary<string, (string TargetUid, DateTimeOffset ExpiresAt)> _tokens = new(StringComparer.Ordinal);
|
||||
|
||||
private Timer? _cleanupTimer;
|
||||
private readonly IDiscoveryPresenceStore _store;
|
||||
private readonly TimeSpan _presenceTtl = TimeSpan.FromMinutes(5);
|
||||
private readonly TimeSpan _tokenTtl = TimeSpan.FromMinutes(2);
|
||||
|
||||
public DiscoveryPresenceService(ILogger<DiscoveryPresenceService> logger)
|
||||
public DiscoveryPresenceService(ILogger<DiscoveryPresenceService> logger, IDiscoveryPresenceStore store)
|
||||
{
|
||||
_logger = logger;
|
||||
_store = store;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cleanupTimer = new Timer(_ => Cleanup(), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cleanupTimer?.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
foreach (var kv in _presence.ToArray())
|
||||
{
|
||||
if (kv.Value.ExpiresAt <= now) _presence.TryRemove(kv.Key, out _);
|
||||
}
|
||||
foreach (var kv in _tokens.ToArray())
|
||||
{
|
||||
if (kv.Value.ExpiresAt <= now) _tokens.TryRemove(kv.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null)
|
||||
{
|
||||
var exp = DateTimeOffset.UtcNow.Add(_presenceTtl);
|
||||
foreach (var h in hashes.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
_presence[h] = (uid, exp, displayName);
|
||||
}
|
||||
_store.Publish(uid, hashes, displayName);
|
||||
_logger.LogDebug("Discovery presence published for {uid} with {n} hashes", uid, hashes.Count());
|
||||
}
|
||||
|
||||
public (bool Found, string Token, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||
{
|
||||
if (_presence.TryGetValue(hash, out var entry))
|
||||
{
|
||||
if (string.Equals(entry.Uid, requesterUid, StringComparison.Ordinal)) return (false, string.Empty, null);
|
||||
var token = Guid.NewGuid().ToString("N");
|
||||
_tokens[token] = (entry.Uid, DateTimeOffset.UtcNow.Add(_tokenTtl));
|
||||
return (true, token, entry.DisplayName);
|
||||
}
|
||||
return (false, string.Empty, null);
|
||||
return _store.TryMatchAndIssueToken(requesterUid, hash);
|
||||
}
|
||||
|
||||
public bool ValidateToken(string token, out string targetUid)
|
||||
{
|
||||
targetUid = string.Empty;
|
||||
if (_tokens.TryGetValue(token, out var info))
|
||||
{
|
||||
if (info.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
{
|
||||
targetUid = info.TargetUid;
|
||||
return true;
|
||||
}
|
||||
_tokens.TryRemove(token, out _);
|
||||
}
|
||||
return false;
|
||||
return _store.ValidateToken(token, out targetUid);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
(_store as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user