Repository navigation
Owner checks on invite administration routes - #3066
Merged
Merged
Conversation
Restrict the invite-administration routes to the conversation's owner or a moderator on the owner's site, the same ownership gate the data export and conversation UUID routes already use. Observable changes: - GET and POST /api/v3/treevite/waves, GET /api/v3/treevite/invites and GET /api/v3/treevite/invites/csv return 403 to callers who are neither the owner nor a site moderator (previously any valid token received 200/201). The admin UI, operated by the owner or a moderator, is unaffected. - POST /api/v3/users/invite returns 403 to the same non-owner callers. - GET /api/v3/conversations no longer treats an `xid` query parameter specially: listing conversations no longer creates single-use invite rows as a side effect, and each returned `url` is the ordinary conversation url. No in-repo client sends `xid` to this route. - POST /api/v3/treevite/acceptInvite records the invite against a participant in the requested conversation (creating one when needed) rather than a participant id carried by a token issued for a different conversation. Legitimate first-time accepts continue to succeed. The single-use invite insert is now a parameterised multi-row statement. Tests (server integration + unit): - For each gated route: a signed-in non-owner and an anonymous participant token from another conversation receive 403; the owner and a site moderator succeed. - acceptInvite: a token from another conversation is scoped to the requested conversation and is not rejected. - GET /api/v3/conversations with an xid writes no invite rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Contributor
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 changes
/api/v3/treevite/waves, GET/api/v3/treevite/invites, GET/api/v3/treevite/invites/csvnow return 403 to any caller who is not the conversation's owner or a moderator on the owner's site. Previously any valid token — including an anonymous participant token from another conversation — received 200/201. The admin console, operated by the owner or a site moderator, is unaffected./api/v3/users/invitenow returns 403 to the same non-owner callers (previously any signed-in user could send join invitations for any conversation)./api/v3/conversationsno longer treats anxidquery parameter specially. Listing conversations no longer creates single-use invite rows as a side effect of the read, and each returnedurlis the ordinary conversation url. No in-repo client sendsxidto this route./api/v3/treevite/acceptInviterecords the accepted invite against a participant that belongs to the requested conversation (creating one when the authenticated user has none there yet) instead of a participant id carried by a token that was issued for a different conversation. Legitimate first-time accepts continue to succeed.What changed
The invite-administration routes now apply the same
isModerator(zid, uid)ownership gate (owner, or a moderator on the owner's site) the data export and conversation UUID routes already use, refusing non-owners before any read or write.server/src/invites/treevites.ts— owner/moderator check onhandle_POST_treevite_waves,handle_GET_treevite_waves,handle_GET_treevite_invites,handle_GET_treevite_invites_csv; participant scoping inhandle_POST_treevite_acceptInvite(resolve the participant for the requested conversation on the primary, creating it for a first-time accept, instead of trusting the token's pid).server/src/routes/users.ts— owner/moderator check onhandle_POST_users_invite.server/src/routes/conversations.ts+server/app.ts— remove thexidside effect from the conversations listing. Chosen over gating it to moderators: it is a write performed by a read route with no in-repo caller, so removing it is the smaller, correct change.server/src/invites/suzinvites.ts— the single-use invite insert is now a parameterised multi-row statement.New error codes:
polis_err_treevite_not_conversation_owner,polis_err_treevite_auth_check,polis_err_sending_invite_not_conversation_owner,polis_err_sending_invite_auth_check.Tests
Server integration + unit tests, run as CI does (the
docker-compose.test.ymlstack,npm run contract:check, and the full jest suite with--ci --coverage --maxWorkers=2):acceptInvite: a token carrying a participant id from another conversation is scoped to the requested conversation and is not rejected; the invite and login code are recorded against a participant in the requested conversation.GET /api/v3/conversationswith anxidwrites no invite rows.Full jest suite: 1827 passed, 4 skipped, 0 failed.
contract:check: pass.Participation path
No change to votes, comments or the next-statement call, and no new query, lock or pool work on them. The only added read is on
acceptInvite, a join-time route: one indexed participant lookup on the primary.Also worth knowing: an outside integrator that used
GET /api/v3/conversations?xid=to mint single-use/ot/links now gets ordinary conversation urls and no rows are written; links already minted still work. OnacceptInvite, a signed-in caller whose token carries a positive pid for another conversation is resolved to (or given) a participant in the requested conversation. Unchanged and pending a separate decision: single-use invite codes still come fromMath.random, and invitee emails are still stored as XIDs.🤖 Generated with Claude Code
https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s