diff --git a/Backend/Remora.Discord.Caching.Redis/Services/RedisCacheProvider.cs b/Backend/Remora.Discord.Caching.Redis/Services/RedisCacheProvider.cs
index fd1fdb10cf..c602d5aa29 100644
--- a/Backend/Remora.Discord.Caching.Redis/Services/RedisCacheProvider.cs
+++ b/Backend/Remora.Discord.Caching.Redis/Services/RedisCacheProvider.cs
@@ -21,6 +21,8 @@
//
using System;
+using System.Security.Cryptography;
+using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -29,6 +31,7 @@
using Microsoft.Extensions.Options;
using Remora.Discord.Caching.Abstractions;
using Remora.Discord.Caching.Abstractions.Services;
+using Remora.Discord.Rest;
using Remora.Results;
namespace Remora.Discord.Caching.Redis.Services;
@@ -41,16 +44,40 @@ public class RedisCacheProvider : ICacheProvider
{
private readonly IDistributedCache _cache;
private readonly JsonSerializerOptions _jsonOptions;
+ private readonly string? _tokenHash;
///
/// Initializes a new instance of the class.
///
/// The redis cache.
/// The JSON options.
- public RedisCacheProvider(IDistributedCache cache, IOptionsMonitor jsonOptions)
+ /// The token store, if one is available.
+ public RedisCacheProvider
+ (
+ IDistributedCache cache,
+ IOptionsMonitor jsonOptions,
+ ITokenStore? tokenStore = null
+ )
{
_cache = cache;
_jsonOptions = jsonOptions.Get("Discord");
+
+ if (tokenStore is null)
+ {
+ _tokenHash = null;
+ return;
+ }
+
+ using var hasher = SHA256.Create();
+ var hashBuilder = new StringBuilder(64);
+ var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(tokenStore.Token));
+
+ foreach (var value in hash)
+ {
+ hashBuilder.Append(value.ToString("x2"));
+ }
+
+ _tokenHash = hashBuilder.ToString();
}
///
@@ -77,7 +104,7 @@ public virtual async ValueTask CacheAsync
var serialized = JsonSerializer.SerializeToUtf8Bytes(instance, _jsonOptions);
- await _cache.SetAsync(key.ToCanonicalString(), serialized, options, ct);
+ await _cache.SetAsync(CreateTokenScopedKey(key), serialized, options, ct);
}
///
@@ -95,7 +122,7 @@ public virtual async ValueTask> RetrieveAsync
)
where TInstance : class
{
- var keyString = key.ToCanonicalString();
+ var keyString = CreateTokenScopedKey(key);
var value = await _cache.GetAsync(keyString, ct);
@@ -114,7 +141,7 @@ public virtual async ValueTask> RetrieveAsync
///
public async ValueTask EvictAsync(CacheKey key, CancellationToken ct = default)
{
- var keyString = key.ToCanonicalString();
+ var keyString = CreateTokenScopedKey(key);
var existingValue = await _cache.GetAsync(keyString, ct);
@@ -143,7 +170,7 @@ public virtual async ValueTask> EvictAsync
)
where TInstance : class
{
- var keyString = key.ToCanonicalString();
+ var keyString = CreateTokenScopedKey(key);
var existingValue = await _cache.GetAsync(keyString, ct);
@@ -158,4 +185,13 @@ public virtual async ValueTask> EvictAsync
return deserialized;
}
+
+ ///
+ /// Creates a cache key scoped to a specific token.
+ ///
+ /// The key.
+ /// The scoped key.
+ private string CreateTokenScopedKey(CacheKey key) => _tokenHash is not null
+ ? $"{_tokenHash}:{key.ToCanonicalString()}"
+ : key.ToCanonicalString();
}
diff --git a/Backend/Remora.Discord.Caching/Services/CacheService.cs b/Backend/Remora.Discord.Caching/Services/CacheService.cs
index 6253cc5e0b..e872ef2066 100644
--- a/Backend/Remora.Discord.Caching/Services/CacheService.cs
+++ b/Backend/Remora.Discord.Caching/Services/CacheService.cs
@@ -45,7 +45,11 @@ public class CacheService
///
/// The cache provider.
/// The cache settings.
- public CacheService(ICacheProvider cacheProvider, ImmutableCacheSettings cacheSettings)
+ public CacheService
+ (
+ ICacheProvider cacheProvider,
+ ImmutableCacheSettings cacheSettings
+ )
{
_cacheProvider = cacheProvider;
_cacheSettings = cacheSettings;
diff --git a/Backend/Remora.Discord.Rest/Caching/MemoryCacheProvider.cs b/Backend/Remora.Discord.Rest/Caching/MemoryCacheProvider.cs
index 8d84d0acf5..665c3454b1 100644
--- a/Backend/Remora.Discord.Rest/Caching/MemoryCacheProvider.cs
+++ b/Backend/Remora.Discord.Rest/Caching/MemoryCacheProvider.cs
@@ -20,6 +20,8 @@
// along with this program. If not, see .
//
+using System.Security.Cryptography;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
@@ -37,14 +39,33 @@ namespace Remora.Discord.Rest.Caching;
public class MemoryCacheProvider : ICacheProvider
{
private readonly IMemoryCache _memoryCache;
+ private readonly string? _tokenHash;
///
/// Initializes a new instance of the class.
///
/// The memory cache.
- public MemoryCacheProvider(IMemoryCache memoryCache)
+ /// The token store, if one is available.
+ public MemoryCacheProvider(IMemoryCache memoryCache, ITokenStore? tokenStore = null)
{
_memoryCache = memoryCache;
+
+ if (tokenStore is null)
+ {
+ _tokenHash = null;
+ return;
+ }
+
+ using var hasher = SHA256.Create();
+ var hashBuilder = new StringBuilder(64);
+ var hash = hasher.ComputeHash(Encoding.UTF8.GetBytes(tokenStore.Token));
+
+ foreach (var value in hash)
+ {
+ hashBuilder.Append(value.ToString("x2"));
+ }
+
+ _tokenHash = hashBuilder.ToString();
}
///
@@ -57,7 +78,7 @@ public ValueTask CacheAsync
)
where TInstance : class
{
- _memoryCache.Set(key, instance, options);
+ _memoryCache.Set(CreateTokenScopedKey(key), instance, options);
return default;
}
@@ -66,7 +87,7 @@ public ValueTask CacheAsync
public ValueTask> RetrieveAsync(CacheKey key, CancellationToken ct = default)
where TInstance : class
{
- if (_memoryCache.TryGetValue(key, out var instance))
+ if (_memoryCache.TryGetValue(CreateTokenScopedKey(key), out var instance))
{
return new(instance);
}
@@ -77,12 +98,12 @@ public ValueTask> RetrieveAsync(CacheKey key, Cance
///
public ValueTask EvictAsync(CacheKey key, CancellationToken ct = default)
{
- if (!_memoryCache.TryGetValue(key, out _))
+ if (!_memoryCache.TryGetValue(CreateTokenScopedKey(key), out _))
{
return new(new NotFoundError($"The key \"{key}\" did not contain a value in cache."));
}
- _memoryCache.Remove(key);
+ _memoryCache.Remove(CreateTokenScopedKey(key));
return new(Result.FromSuccess());
}
@@ -90,12 +111,19 @@ public ValueTask EvictAsync(CacheKey key, CancellationToken ct = default
public ValueTask> EvictAsync(CacheKey key, CancellationToken ct = default)
where TInstance : class
{
- if (!_memoryCache.TryGetValue(key, out TInstance? existingValue))
+ if (!_memoryCache.TryGetValue(CreateTokenScopedKey(key), out TInstance? existingValue))
{
return new(new NotFoundError($"The key \"{key}\" did not contain a value in cache."));
}
- _memoryCache.Remove(key);
+ _memoryCache.Remove(CreateTokenScopedKey(key));
return new(existingValue);
}
+
+ ///
+ /// Creates a cache key scoped to a specific token.
+ ///
+ /// The key.
+ /// The scoped key.
+ private object CreateTokenScopedKey(CacheKey key) => (_tokenHash, key);
}