Normalize shared_with email casing across all project access paths

Project sharing in Mike was silently failing for users whose stored email casing didn't match the lookup. bmersereau traces the root cause to three different normalization rules scattered across four endpoints, extracts them into a single access module, and routes every project route through it.

multi-tenantworkflow

The inconsistency was specific. The single-project GET ran a case-sensitive membership check against shared_with. The project list query filtered on the raw, un-normalized requester email. POST stored entries verbatim. PATCH was already lowercasing. The result: legitimate shared users got denied access depending on which casing path their email had taken on the way in.

The fix extracts two helpers into a dedicated access module: one to normalize a shared_with array on write, one to do a case-insensitive membership check on read. Both are small, focused, and independently testable. Every project endpoint now routes through them.

The write normalization does more than lowercase. It also deduplicates entries and drops empty strings, which is light cleanup bmersereau folded in alongside the casing fix.

Eight unit tests cover the obvious edges: lowercasing, dedup, empty entries, null and undefined emails, and the case-insensitive membership check. Before this change those tests couldn't exist because the normalization logic was inline, duplicated, and drifting across three handlers.

So what Worth a look if you allow project sharing by email and haven't verified that your casing handling is consistent across all write and read paths. The testability improvement alone is a good reason to take the pattern even if you haven't hit the bug.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?