fix(guests): drop a stored identity when a spent invite names someone else

A guest coming back through their own already-redeemed link is the
ordinary #1265 case, and the identity the device holds is theirs. The same
link opened on a shared device that holds another guest's identity is not:
the redemption 409s, ensureIdentity() falls through to the stored identity,
and the visitor's likes are filed under the previous person.

The two cases were indistinguishable client-side, so the 409/410 body now
carries the invite's guest_id. On a mismatch the stored identity is cleared
and the visitor is asked who they are. A response without guest_id keeps
the previous behaviour.
This commit is contained in:
Paul Nothaft
2026-09-02 10:22:55 +02:00
parent 7a1ea842e4
commit f2f40893c1
2 changed files with 30 additions and 4 deletions
+21 -1
View File
@@ -187,7 +187,27 @@ export const GuestIdentityProvider: React.FC<GuestIdentityProviderProps> = ({
const newUrl = window.location.pathname + (newSearch ? `?${newSearch}` : '') + window.location.hash;
window.history.replaceState({}, '', newUrl);
} catch (error) {
// Silently fail invalid invites; user will fall back to normal prompt.
// A spent (409) or revoked (410) invite is the normal way a guest comes
// back through their own emailed link, and the identity this device
// holds is then theirs — keep it. But the same link opened on a shared
// device that holds SOMEONE ELSE's identity must not quietly act as
// that someone: the server names the invite's guest on those two
// responses precisely so the two cases can be told apart. On a
// mismatch the stored identity is dropped, so the visitor is asked
// who they are instead of having their likes filed under the previous
// person. A response without guest_id (older backend) keeps today's
// behaviour.
const status = (error as { response?: { status?: number; data?: { guest_id?: unknown } } })
.response;
const invitedGuestId = status?.data?.guest_id;
if ((status?.status === 409 || status?.status === 410) && typeof invitedGuestId === 'number') {
const stored = getGuestIdentity(slug);
if (stored && stored.id !== invitedGuestId) {
clearGuestIdentity(slug);
setIdentity(null);
}
}
// Otherwise fail silently; the visitor falls back to the normal prompt.
// eslint-disable-next-line no-console
console.warn('Failed to redeem invite token', error);
} finally {