Refactor token handling in ConfirmAsync method for improved backward compatibility and introduce FindTokenAsync helper method

This commit is contained in:
warrenchen 2026-02-04 17:45:32 +09:00
parent e4af8f067f
commit db50810d15

View File

@ -74,10 +74,12 @@ public sealed class NewsletterService : INewsletterService
public async Task<SubscriptionDto?> ConfirmAsync(string token)
{
var tokenHash = HashToken(token, ConfirmTokenPurpose);
var confirmToken = await _dbContext.UnsubscribeTokens
.Include(t => t.Subscription)
.FirstOrDefaultAsync(t => t.TokenHash == tokenHash && t.ConsumedAt == null);
var confirmToken = await FindTokenAsync(token, ConfirmTokenPurpose);
if (confirmToken is null && token.Contains(' '))
{
// Backward compatibility: legacy Base64 tokens may decode '+' as space in query string.
confirmToken = await FindTokenAsync(token.Replace(' ', '+'), ConfirmTokenPurpose);
}
if (confirmToken?.Subscription is null)
{
@ -170,7 +172,7 @@ public sealed class NewsletterService : INewsletterService
private static string CreateToken()
{
var bytes = RandomNumberGenerator.GetBytes(32);
return Convert.ToBase64String(bytes);
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
private static string HashToken(string token, string purpose)
@ -200,4 +202,12 @@ public sealed class NewsletterService : INewsletterService
subscription.Preferences.RootElement.Clone(),
subscription.CreatedAt);
}
private Task<UnsubscribeToken?> FindTokenAsync(string token, string purpose)
{
var tokenHash = HashToken(token, purpose);
return _dbContext.UnsubscribeTokens
.Include(t => t.Subscription)
.FirstOrDefaultAsync(t => t.TokenHash == tokenHash && t.ConsumedAt == null);
}
}