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