Validate invite codes before showing the signup form#1265
Conversation
Greptile SummaryThis PR adds upfront invite-code validation to the
Confidence Score: 4/5Safe to merge once the open discussion about transient server errors rendering the "Invite not valid" dead-end is resolved or accepted as a known trade-off. The backend change is minimal and read-only, the three-state guard on the frontend is correct, and the test coverage is solid. The one open thread on this PR identifies a real UX defect in the error-handling path — a DB hiccup or brief network blip silently maps to the same dead-end card shown for a genuinely invalid code, giving a legitimate invite holder no way to retry. Until that is addressed or explicitly accepted, the change is not fully production-ready. apps/host-selfhost/web/routes/public/join.$code.tsx — the fetch rejection and non-2xx response paths both resolve to the same "Invite not valid" state; the open thread on this file describes the concern in detail. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Browser
participant JoinPage
participant InviteStatusAPI as GET /api/invite-status/:code
participant DB as invite_code table
Browser->>JoinPage: "Navigate to /join/<code>"
JoinPage->>JoinPage: setState("checking") → show Loading…
JoinPage->>InviteStatusAPI: "fetch /api/invite-status/<code>"
InviteStatusAPI->>DB: "SELECT * WHERE code=? AND used_at IS NULL"
DB-->>InviteStatusAPI: row or null
InviteStatusAPI-->>JoinPage: "{ valid: true|false } (HTTP 200)"
alt "valid === true"
JoinPage->>JoinPage: setState("valid") → show signup form
Browser->>JoinPage: Fill form and submit
JoinPage->>JoinPage: authClient.signUp.email(...)
JoinPage-->>Browser: Redirect to "/"
else "valid === false"
JoinPage->>JoinPage: setState("invalid") → show "Invite not valid" card
else "fetch rejected or response.ok===false"
JoinPage->>JoinPage: setState("invalid") → show "Invite not valid" card
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Browser
participant JoinPage
participant InviteStatusAPI as GET /api/invite-status/:code
participant DB as invite_code table
Browser->>JoinPage: "Navigate to /join/<code>"
JoinPage->>JoinPage: setState("checking") → show Loading…
JoinPage->>InviteStatusAPI: "fetch /api/invite-status/<code>"
InviteStatusAPI->>DB: "SELECT * WHERE code=? AND used_at IS NULL"
DB-->>InviteStatusAPI: row or null
InviteStatusAPI-->>JoinPage: "{ valid: true|false } (HTTP 200)"
alt "valid === true"
JoinPage->>JoinPage: setState("valid") → show signup form
Browser->>JoinPage: Fill form and submit
JoinPage->>JoinPage: authClient.signUp.email(...)
JoinPage-->>Browser: Redirect to "/"
else "valid === false"
JoinPage->>JoinPage: setState("invalid") → show "Invite not valid" card
else "fetch rejected or response.ok===false"
JoinPage->>JoinPage: setState("invalid") → show "Invite not valid" card
end
Reviews (3): Last reviewed commit: "Validate join invites before showing sig..." | Re-trigger Greptile |
| () => { | ||
| if (alive) setInviteState("invalid"); | ||
| }, | ||
| ); | ||
| return () => { | ||
| alive = false; | ||
| }; | ||
| }, [code]); | ||
|
|
||
| const submit = async (event: FormEvent) => { |
There was a problem hiding this comment.
Network / server errors silently shown as "Invite not valid"
Both a rejected fetch (network down, DNS failure) and a non-2xx response (DB error returning a SystemError) collapse into setInviteState("invalid"), which renders the "Invite not valid" card. A user holding a perfectly good invite code who visits during a brief server hiccup will be told their link is expired or invalid and given no way to retry. The fetch-rejection branch and the !response.ok body-override both need to distinguish between "the server said invalid" and "we couldn't reach the server at all" — a separate "error" state with a retry prompt would prevent the misleading dead-end.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 2c1d30c | Jul 02 2026, 08:15 PM |
Cloudflare previewTorn down — the PR is closed. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | 2c1d30c | Commit Preview URL Branch Preview URL |
Jul 02 2026, 08:13 PM |
b0f4737 to
bc8b745
Compare
2808d62 to
f867dbe
Compare
bc8b745 to
f1afa78
Compare
@executor-js/cli
@executor-js/config
@executor-js/execution
@executor-js/sdk
@executor-js/codemode-core
@executor-js/runtime-quickjs
@executor-js/plugin-file-secrets
@executor-js/plugin-graphql
@executor-js/plugin-keychain
@executor-js/plugin-mcp
@executor-js/plugin-onepassword
@executor-js/plugin-openapi
executor
commit: |
f867dbe to
62d477a
Compare
2695ec3 to
a521c37
Compare
9ae67c8 to
b0037e9
Compare
a521c37 to
e977b28
Compare
e977b28 to
2c1d30c
Compare
Visiting /join/
with an invalid or expired code rendered the full You've been invited signup form and only failed after the user filled it in and submitted.The join page now validates the code on mount via a new lightweight GET invite-status endpoint (read-only, does not burn the code). While checking it shows the loading state; an invalid code shows an Invite not valid card with no form.
Verified with an extended invites test covering the new endpoint plus manual browser runs for valid and invalid codes. Typecheck is green.
Stacked on #1264.