Allow invite-gated social signup in production - #773
Conversation
📝 WalkthroughWalkthroughSocial 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. ChangesOAuth invite-gated signup
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
🔎 Preview deployed: https://kody-pr-773.kody-a99.workers.dev Worker: Mocks:
|
There was a problem hiding this comment.
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 winPrevent invite code leakage on username generation failures.
If
getAvailableUsernameFromBasethrows an error (e.g., due to a transient database issue), the execution will bypass thetry/catchblock. BecauseconsumeInviteCodehas already incremented the invite'suse_countat this point, the invite will be permanently burned without creating the user account.Move the username and stable user ID generation inside the
tryblock to guarantee that thecatchblock 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
📒 Files selected for processing (10)
docs/contributing/architecture/authentication.mddocs/contributing/architecture/primitives.yamldocs/contributing/social-login.mdpackages/worker/client/routes/login.tsxpackages/worker/client/social-sign-in.node.test.tspackages/worker/client/social-sign-in.tspackages/worker/src/app/handlers/auth-provider.node.test.tspackages/worker/src/app/handlers/auth-provider.tspackages/worker/src/app/oauth-login-errors.tspackages/worker/src/app/oauth-login-state.ts
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>
b97c912 to
e8dae20
Compare
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.
inviteCodeinkody_oauth_loginstate fromPOST /auth/:provideroauthError=invite-*); non-production stays open without oneTest plan
invite-invalid; existing connection login still works without invitebuildProviderStartPathincludes invite query param/signup→ “I have a code” → enter invite → Continue with GitHub/GoogleSystem recap — extends existing primitives (medium risk)
Mode: recap · Base:
main@a58fa1d0· Head:e8dae200Classification: extends — OAuth signup callback now invite-consumes like password signup; signed OAuth state and login UI carry the invite code.
Primitives touched
app-sessionsinvite-*oauth error codesapp-uiinviteCodeinto social startd1-app-dbinvitesconsume/release path as password signupSystem 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).
Change flow
Before / after
invite-requiredSummary by CodeRabbit
oauthErroroutcomes (e.g., invite-required/invalid/revoked/expired/exhausted).