Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ private static string CreateToken(
var normalizedMessageIds = messageIds
.Where(id => !string.IsNullOrWhiteSpace(id))
.Select(id => id.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(id => id, StringComparer.OrdinalIgnoreCase)
.Distinct(StringComparer.Ordinal)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep token dedupe aligned with preview normalization

Switching token deduplication to StringComparer.Ordinal makes case-variant IDs (for example "msg-1" and "MSG-1") hash as separate entries, but preview generation still normalizes IDs case-insensitively in MailMessageActionPreviewService.NormalizeMessageIds before issuing the token. That means a client can request a preview, then execute the same action payload with the returned token and hit confirmation_token_mismatch because validation now recomputes a different token from the raw request list. This is a regression introduced by the comparer change and breaks preview→execute confirmation flows for inputs that differ only by casing.

Useful? React with 👍 / 👎.

.OrderBy(id => id, StringComparer.Ordinal)
.ToArray();

var payload = string.Join("|", new[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Mailozaurr.Application;
using Xunit;

namespace Mailozaurr.Tests;

public class ApplicationMessageActionConfirmationTokensTests {
[Fact]
public void CreateDeleteTokenPreservesMessageIdCasing() {
var lowerToken = MessageActionConfirmationTokens.CreateDeleteToken(
"work-imap",
"shared@example.com",
"Inbox",
new[] { "abc123" });

var upperToken = MessageActionConfirmationTokens.CreateDeleteToken(
"work-imap",
"shared@example.com",
"Inbox",
new[] { "ABC123" });

Assert.NotEqual(lowerToken, upperToken);
}

[Fact]
public void CreateDeleteTokenIgnoresMessageIdOrdering() {
var firstToken = MessageActionConfirmationTokens.CreateDeleteToken(
"work-imap",
"shared@example.com",
"Inbox",
new[] { "msg-b", "msg-a" });

var secondToken = MessageActionConfirmationTokens.CreateDeleteToken(
"work-imap",
"shared@example.com",
"Inbox",
new[] { "msg-a", "msg-b" });

Assert.Equal(firstToken, secondToken);
}
}
Loading