Repository navigation
Resolve the participant for the requested conversation before recording a vote - #3053
Merged
Merged
Conversation
…tion A participant token minted for one conversation can be used to POST a vote to a different conversation. Because participant ids are assigned per conversation, the vote is stored against whichever participant holds that id number in the target conversation, i.e. attributed to a different person. Integration tests exercise anonymous, XID and standard-user participant tokens (all affected), the raw OIDC access token (a control that resolves the id fresh), the comment path (a control that re-resolves the id by user), and a token carrying an id that does not exist in the target conversation. Each reproduction asserts the correct behaviour, so it is red until the defect is fixed. Votes are written only through the vote route. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…ng a vote A participant token carries the pid it was issued with, and pids are assigned per conversation. ensureParticipant copied the request's uid and pid into locals before the conversation-mismatch handler cleared them, so a token presented in a different conversation kept its pid, and the nonnegative-pid fast path used that pid as the requested conversation's participant. The identity is now read after the handler has run, and a token issued for another conversation never supplies this conversation's pid: the participant is looked up (or created) for (zid, uid), which is what the handler intends. A token used in the conversation it was issued in takes the same path as before. The integration suite now covers: same-conversation tokens (anonymous, XID, standard-user) voting as themselves with no new participant and no new token; cross-conversation tokens voting as a participant of their own in the requested conversation, not the holder of the same pid number there and not an orphan pid, with a token for that conversation in the response; a cross-conversation token whose pid has no participant there; participationInit with a cross-conversation token; and the OIDC-token and comment-route controls. Votes are cast only through the vote route. The existing "XID users should be able to participate in multiple conversations" test voted with a token minted for a simulated uid that exists in no users row; it now holds the token the server issued for a real XID participant, as the flow it describes does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…lved The JWT extractor flagged a token as being for another conversation by comparing its conversation id with the request's query string first and the body second, while POST /votes resolves the conversation from the body alone. A token issued in X, presented with X in the query string and Y in the body, was therefore not flagged, kept X's pid, and the vote was recorded in Y under that pid. The extractor now always records the token's conversation id, and ensureParticipant decides the token's scope against the zid the route resolved: the same conversation id string as the one the route's parameter parsing read is the same conversation and costs nothing; otherwise the token's conversation id is resolved (an LRU-cached read) and the zids are compared. Every genuine mismatch now also requests a replacement token, so a response carries this conversation's token even when the participant already existed (the XID case that keeps its user). In a conversation that requires an invite, an identity without a pid for this conversation (a token issued elsewhere, or no participant token) had its first-join refusal applied before its existing membership was looked up, so an existing participant presenting a token from another conversation was refused. The membership is now looked up by (zid, uid) first, and the pid found is passed on so nothing is read twice; a newcomer without an invite is still refused and the owner still joins. The integration suite adds: a same-conversation vote with another conversation's id in the query string; the query/body split for all three token kinds, with the pid held by someone in the other conversation and with it absent; presenting the same token a second time; and the invite-only conversation with an existing participant, a newcomer without an invite and the owner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…ke the token's pid number When nobody holds the token's pid number in the other conversation, the participant created there for the caller is allocated the next number, which can be the same one; the assertion for that case now checks that the vote's participant row exists and was created for the vote, not that its number differs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Delphi Coverage Report
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Observable change
Participant tokens are now scoped against the conversation resolved by the route, including requests whose query and body name different conversations. A token for another conversation cannot supply its participant ID: membership is resolved for the target conversation and a replacement token is requested. Existing members of invite-only conversations are resolved before first-join restrictions apply.
The ordinary same-conversation-ID path adds no database query. Cross-conversation requests may perform cached conversation resolution, existing authorization reads and an indexed membership lookup; no report, export or external-service work is added to participation.
Tests
server/__tests__/integration/vote-misattribution.test.ts(23 cases): same-conversation tokens of every kind, cross-conversation tokens, conflicting query and body conversation ids, invite-only existing members, newcomers and owners, participationInit, and OIDC and comment-route controls.anonymous-jwt.test.tsnow builds its XID participant through the real participation flow.🤖 Generated with Claude Code