implement workshop side of gatekeeper sharing api - #479
Conversation
Preview:
|
| if (userId === this.#userId.name) return null; | ||
| let verifier = await this.users.getByName(userId).getUniqueGatekeeperUserVerifier(gatekeeperId); | ||
| if (!verifier) return null; | ||
| return { verifier, profile: this.ctx.exports.GatekeeperUserProfileImpl({ props: { userId } }) }; |
There was a problem hiding this comment.
Returning this raw verifier from AuthenticatedApi hands it to arbitrary browser code, not only to the gatekeeper that minted it. Cap’n Web stubs expose every prototype method at runtime regardless of the empty GatekeeperUserVerifier interface, so an authenticated user can call this with e.g. gatekeeperId = "google", cast the returned verifier, and invoke hasDocAccess() / verifyDriveFiles() using the target user’s credentials. The caller-controlled vendor is not even tied to an app returned by getGatekeeperApp(). This breaks the existing same-vendor handoff invariant and creates a cross-user ACL/provider-request oracle. Please bind selection to the exact opened management-app capability and arrange for the verifier to be consumed server-to-server rather than returning it over the browser RPC.
| (value: string) => authenticatedApi.searchUsers(value, pickedIds), [authenticatedApi, pickedIds]) | ||
|
|
||
| const pick = (user: UserDirectoryRecord) => { | ||
| authenticatedApi.selectGatekeeperUser(gatekeeperId, user.id).then( |
There was a problem hiding this comment.
This request can settle after Cancel/unmount, or after Done has transferred the currently resolved picks. In either case a successful late response reaches setPicks() after the dialog is gone and neither returned stub is disposed, leaking both remote capabilities for the lifetime of the WebSocket session. The same missing in-flight tracking permits double-click duplicates and out-of-order/omitted selections. Track requests against the picker session, reserve each user/order when the request starts, prevent completion while requests are pending, and dispose any result that arrives after completion or unmount.
| return ( | ||
| <Dialog.Root open onOpenChange={(open) => { if (!open) cancel() }}> | ||
| <Dialog | ||
| className="responsive-dialog !z-[2147483100] !w-[min(520px,calc(100vw-32px))] bg-kumo-base p-0 !outline-none" |
There was a problem hiding this comment.
This raises only Kumo’s popup. Kumo renders its backdrop as a separate fixed portal sibling with no z-index, while this app iframe can already be at 2147483000 after setPresenting(true). A gatekeeper can therefore open the picker while presented and remain visible and pointer-interactive above the backdrop everywhere outside the trusted popup; iframe clicks do not bubble to the host dialog. Please put the whole dialog portal/backdrop above the iframe or make the iframe inert/non-interactive while the trusted picker is open.
|
Posted 3 actionable inline findings. CI build, tests, and lint are passing. |
WIP
User eligibility isn't checked until the user is selected from search results, so users that are ineligible to receive the share will still appear in search results and fail with a helpful error message when selected. This lets us re-use the same user search API used for workspace sharing. If we wanted to scope search results to just eligible users, the user directory would need to become aware of each user's connected accounts and connection state, which I'm not sure we want.