Skip to content

Allow invite-gated social signup in production - #773

Merged
kody-bot merged 2 commits into
mainfrom
cursor/social-signup-invite-22c9
Jul 17, 2026
Merged

kody-bot merged 2 commits into
mainfrom
cursor/social-signup-invite-22c9

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 17, 2026 •

Copy link
Copy Markdown
Owner

Summary

Production blocked OAuth account creation even when the user had a valid invite. Social signup now works the same way as password signup: the invite signup panel carries the code into the signed OAuth state cookie, and the callback consumes it before creating the account.

  • Carry optional inviteCode in kody_oauth_login state from POST /auth/:provider
  • Production new-account path consumes the invite (missing/invalid → oauthError=invite-*); non-production stays open without one
  • Invite signup UI passes the code into social “Continue with …” buttons
  • Rollback releases a consumed invite if account creation fails mid-flight (including username/stable-id generation)

Test plan

  • Unit: production OAuth signup blocked without invite
  • Unit: production OAuth signup succeeds with valid invite (consumes + audits)
  • Unit: invalid invite → invite-invalid; existing connection login still works without invite
  • Unit: buildProviderStartPath includes invite query param
  • Manual: /signup → “I have a code” → enter invite → Continue with GitHub/Google
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ a58fa1d0 · Head: e8dae200

Classification: extends — OAuth signup callback now invite-consumes like password signup; signed OAuth state and login UI carry the invite code.

Primitives touched

Primitive Group Impact
app-sessions auth extends — production social signup consumes invite from OAuth state; new invite-* oauth error codes
app-ui surfaces composes — invite signup panel passes inviteCode into social start
d1-app-db storage composes — same invites consume/release path as password signup

System map

Invite-gated social signup flows from the signup UI through OAuth state into invite consumption and account creation in D1.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app"]:::touched
	appSessions["app-sessions<br/>Browser sessions"]:::extended
	d1AppDb["d1-app-db<br/>D1 app database"]:::touched
	appUi -->|"inviteCode on POST /auth/:provider"| appSessions
	appSessions -->|"consumeInviteCode on new OAuth account"| d1AppDb
	appSessions -->|"oauth_connections + users insert"| d1AppDb
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Change flow

sequenceDiagram
	participant UI as Signup UI
	participant Start as POST /auth/:provider
	participant Provider as GitHub/Google/X
	participant Callback as GET /auth/:provider/callback
	participant Invites as invites table
	UI->>Start: inviteCode + redirectTo
	Start-->>UI: authorizeUrl + kody_oauth_login cookie
	UI->>Provider: top-level navigate
	Provider->>Callback: code + state
	Callback->>Invites: consume when new account
	Callback-->>UI: kody_session (or oauthError=invite-*)
Loading

Before / after

Path Before After
Production OAuth new account Always invite-required Succeeds when signed state has a valid invite
Non-production OAuth new account Open Unchanged (still open; consumes invite if provided)
Existing connection / email match Sign in / link Unchanged (no invite needed)
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added invite-code support to social sign-up for GitHub, Google, and X, including trimming/forwarding invite codes through the OAuth flow.
    • Production social sign-up is now invite-gated; existing social logins for connected accounts still work without an invite.
    • Enhanced invite-related OAuth errors with clearer oauthError outcomes (e.g., invite-required/invalid/revoked/expired/exhausted).
  • Documentation
    • Updated architecture and social-login docs to explain invite-code gating and how it’s carried during the provider callback.
  • Tests
    • Added unit and end-to-end coverage for invite-gated social signup and new error handling behavior.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Social OAuth signup now carries invite codes from the signup form through the signed OAuth state cookie, enforces invite validity during production account creation, records invite usage, and documents the updated behavior.

Changes

OAuth invite-gated signup

Layer / File(s) Summary
OAuth state and error contracts
packages/worker/src/app/oauth-login-state.ts, packages/worker/src/app/oauth-login-errors.ts
OAuth state stores and normalizes an optional inviteCode, and dedicated invite-related OAuth errors are defined.
Client invite propagation
packages/worker/client/routes/login.tsx, packages/worker/client/social-sign-in.ts, packages/worker/client/social-sign-in.node.test.ts
Signup social-login requests pass trimmed invite codes through the provider-start URL, with URL construction tests covering optional parameters.
Callback invite enforcement
packages/worker/src/app/handlers/auth-provider.ts
Production new-account OAuth callbacks require and consume valid invites, release invite usage on failure or rollback, and audit successful invite use.
Validation and documentation
packages/worker/src/app/handlers/auth-provider.node.test.ts, docs/contributing/social-login.md, docs/contributing/architecture/authentication.md, docs/contributing/architecture/primitives.yaml
Tests cover missing, valid, and invalid invites plus existing-account login; documentation describes invite propagation and gating.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SignupForm
  participant SocialSignIn
  participant AuthProvider
  participant OAuthState
  participant GitHub
  participant InviteStore
  SignupForm->>SocialSignIn: submit inviteCode
  SocialSignIn->>AuthProvider: start OAuth with inviteCode
  AuthProvider->>OAuthState: store normalized inviteCode
  AuthProvider->>GitHub: redirect for authorization
  GitHub-->>AuthProvider: provider callback
  AuthProvider->>OAuthState: read inviteCode
  AuthProvider->>InviteStore: consume invite for new account
  InviteStore-->>AuthProvider: consumption result
  AuthProvider-->>SignupForm: redirect to account or oauthError
Loading

Possibly related PRs

  • kentcdodds/kody#678: Earlier social-login implementation extended here with invite-code round-tripping and invite handling.
  • kentcdodds/kody#683: Shared social-login start/callback plumbing is extended here with invite-code propagation.
  • kentcdodds/kody#728: Invite signup panel behavior supplies the code now passed into social OAuth signup.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling invite-gated social signup in production.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/social-signup-invite-22c9

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kody-bot
kody-bot marked this pull request as ready for review July 17, 2026 05:58
@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-773.kody-a99.workers.dev

Worker: kody-pr-773
D1: kody-pr-773-db
KV: kody-pr-773-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/app/handlers/auth-provider.ts (1)

460-480: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent invite code leakage on username generation failures.

If getAvailableUsernameFromBase throws an error (e.g., due to a transient database issue), the execution will bypass the try/catch block. Because consumeInviteCode has already incremented the invite's use_count at this point, the invite will be permanently burned without creating the user account.

Move the username and stable user ID generation inside the try block to guarantee that the catch block triggers and safely releases the consumed invite upon any unexpected failure.

🛡️ Proposed fix to expand the try block
-			const username = await getAvailableUsernameFromBase(
-				env.APP_DB,
-				profile.username ?? usernameFromEmail(email),
-			)
-			const stableUserId = await createStableUserIdFromEmail(email)
+			let username: string
+			let stableUserId: string
			let newUser: { id: number } | null = null
			try {
+				username = await getAvailableUsernameFromBase(
+					env.APP_DB,
+					profile.username ?? usernameFromEmail(email),
+				)
+				stableUserId = await createStableUserIdFromEmail(email)
				const createdUser = await db.create(
					usersTable,
					{
						username,
						email,
						stable_user_id: stableUserId,
						password_hash: oauthNoUsablePasswordHash,
						email_verified_at: new Date().toISOString(),
					},
					{ returnRow: true },
				)
				newUser = { id: createdUser.id }
			} catch (error) {
				await releaseConsumedInvite()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/app/handlers/auth-provider.ts` around lines 460 - 480,
Move the getAvailableUsernameFromBase and createStableUserIdFromEmail calls into
the existing try block surrounding user creation, keeping their results
available to db.create. Ensure any failure during username or stable ID
generation reaches the existing catch block so releaseConsumedInvite is invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/worker/src/app/handlers/auth-provider.ts`:
- Around line 460-480: Move the getAvailableUsernameFromBase and
createStableUserIdFromEmail calls into the existing try block surrounding user
creation, keeping their results available to db.create. Ensure any failure
during username or stable ID generation reaches the existing catch block so
releaseConsumedInvite is invoked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 73bb84b9-43b2-4b83-b2ff-31bc40b30660

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0cafb and bce4e53.

📒 Files selected for processing (10)
  • docs/contributing/architecture/authentication.md
  • docs/contributing/architecture/primitives.yaml
  • docs/contributing/social-login.md
  • packages/worker/client/routes/login.tsx
  • packages/worker/client/social-sign-in.node.test.ts
  • packages/worker/client/social-sign-in.ts
  • packages/worker/src/app/handlers/auth-provider.node.test.ts
  • packages/worker/src/app/handlers/auth-provider.ts
  • packages/worker/src/app/oauth-login-errors.ts
  • packages/worker/src/app/oauth-login-state.ts

cursoragent and others added 2 commits July 17, 2026 06:12
Carry the invite code in the signed OAuth login state so new accounts can
be created via GitHub/Google/X when a valid cohort invite is supplied,
mirroring password signup invite consumption and rollback.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Keep consumeInviteCode paired with release on any pre-insert failure so
transient username/stable-id errors do not burn a cohort invite.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor
cursor Bot force-pushed the cursor/social-signup-invite-22c9 branch from b97c912 to e8dae20 Compare July 17, 2026 06:12
@kody-bot
kody-bot merged commit e246081 into main Jul 17, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/social-signup-invite-22c9 branch July 17, 2026 06:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants