Skip to content

#239 Allow Admins To Create User Accounts#249

Open
b-at-neu wants to merge 9 commits into
devfrom
239-allow-admins-to-create-user-accounts
Open

#239 Allow Admins To Create User Accounts#249
b-at-neu wants to merge 9 commits into
devfrom
239-allow-admins-to-create-user-accounts

Conversation

@b-at-neu

@b-at-neu b-at-neu commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Closes #239

Summary

  • Adds a "Create user" button to the Users page header (admin-only) that opens a dialog
  • Server action pre-invites the user by creating an app-side User row with email only (no Neon Auth API call); when the invitee completes OTP sign-up, resolveRealUser() links their neonAuthId
  • Duplicate email detection is atomic: bare prisma.user.create with a P2002 unique-constraint catch, matching the same pattern used in resolveRealUser

Changes

  • lib/auth/server.ts — extract upsertAppUser helper (exported); resolveRealUser now delegates to it
  • lib/constants.ts — add createUserSchema (zod): email, optional name, isAdmin (default false)
  • prisma/actions/users.ts — add createUser server action: admin-gate, zod-parse, atomic create + P2002 catch for duplicate email, optional admin flag, revalidatePath('/users')
  • components/features/create-user-dialog.tsx — new client component: FormDialog with Email/Name/Admin fields, toasts on success/error
  • app/(main)/(auth)/users/page.tsx — wire CreateUserDialog into PageHeader actions slot

Testing plan

  • As an admin, navigate to /users and confirm a "Create user" button appears in the header
  • Submit the dialog with a new email and name, Admin off — success toast "User created.", dialog closes, new row appears in the table with no Admin badge
  • Submit with Admin checked — success toast "Admin user created.", new row shows Admin badge
  • Submit with an empty email field — inline validation error "Enter a valid email address.", no request sent
  • Submit with an email that already exists — error toast "A user with this email already exists.", dialog stays open, input preserved
  • Submit with name omitted — user created, table shows email as the primary label (name null)
  • Sign out and attempt to sign in as the newly created user via email OTP — succeeds and lands in the app
  • As a non-admin (or unauthenticated), confirm /users still redirects and the createUser action returns { error: 'Unauthorized' } when called directly

Automated checks

  • prettier: pass
  • eslint: pass (0 warnings)
  • tsc: pass

@b-at-neu b-at-neu self-assigned this Jun 26, 2026
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aplio Ready Ready Preview, Comment Jun 29, 2026 9:47am

@b-at-neu b-at-neu added the ready for review PR ready for review agent label Jun 26, 2026
@b-at-neu b-at-neu added reviewing Review agent working (in-flight) and removed ready for review PR ready for review agent labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Cycle 1 · needs revision

2 open — 1 🟠 Medium, 1 🟡 Low (see inline)

Comment thread prisma/actions/users.ts Outdated
Comment thread prisma/actions/users.ts Outdated
@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed reviewing Review agent working (in-flight) needs revision Review found issues that need fixing labels Jun 27, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 1

fixed R1-M1, R1-L1 · 6fbbee1

@b-at-neu b-at-neu added ready for review PR ready for review agent and removed revising Revise agent working (in-flight) labels Jun 27, 2026
@b-at-neu b-at-neu added reviewing Review agent working (in-flight) and removed ready for review PR ready for review agent labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Cycle 2 · approved

2 open — ⚪ Nit (see inline) — R1-M1 and R1-L1 both resolved; no Critical/Medium/Low remain

Comment thread components/features/create-user-dialog.tsx Outdated
Comment thread components/features/create-user-dialog.tsx Outdated
@b-at-neu b-at-neu added approved Review passed, ready to merge and removed reviewing Review agent working (in-flight) labels Jun 27, 2026
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision needed — bug: "Failed to create auth user"

Vercel logs show an unhandled throw with message "Failed to create auth user" as soon as the create user form is submitted. The authServer.admin.createUser call is failing and the error is not being mapped to a user-facing { error } response.

Required investigation and fix:

  1. Check whether authServer.admin is actually available on this Neon Auth instance. The admin plugin must be enabled on the backend — if authServer.admin is undefined or the admin endpoint is not accessible, calling .createUser() will throw or return an error that the current code doesn't handle gracefully.

  2. Inspect the actual return shape of authServer.admin.createUser() at runtime. The plan assumed { data, error } convention, but confirm this matches reality. If it throws rather than returning { error }, the try/catch logic needs to be updated.

  3. Any failure from authServer.admin.createUser that is NOT a user-actionable error (duplicate email, invalid input) should produce a toast "Something went wrong. Please try again." rather than an unhandled server throw. The current code appears to be throwing past the dialog's catch handler.

  4. Add explicit handling for the case where authServer.admin is unavailable (undefined check before calling), returning { error: 'User creation is not available.' } rather than throwing.

Do not change the duplicate-email { error } path, zod validation, auth gate, or transaction logic — only fix the error-handling path for unexpected authServer.admin.createUser failures.

@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed approved Review passed, ready to merge needs revision Review found issues that need fixing labels Jun 27, 2026
@b-at-neu
b-at-neu force-pushed the 239-allow-admins-to-create-user-accounts branch from 6fbbee1 to 3ce8566 Compare June 27, 2026 09:43
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 2

fixed R2-N1, R2-N2 · 3ce8566

@b-at-neu b-at-neu added ready for review PR ready for review agent and removed revising Revise agent working (in-flight) labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Bug Fix Required

Cycle 3 — 1 Critical finding


R3-C1 · Critical — authServer.admin.createUser failure throws instead of returning a user-facing error

Vercel logs show an unhandled server throw with message "Failed to create auth user" as soon as the create user form is submitted. The current code does not handle the case where authServer.admin.createUser fails for reasons other than duplicate email.

Required fixes:

  1. Guard against unavailable admin API. If authServer.admin is undefined or the method doesn't exist, the action throws with no useful message. Add a check before calling: if the admin API is unavailable, return { error: 'User creation is not available.' } rather than throwing.

  2. Catch all authServer.admin.createUser failures gracefully. The current error-mapping only handles duplicate-email error codes. Any other failure (network error, auth backend rejection, invalid credentials, admin plugin not enabled) falls through to a raw throw. Wrap the entire call in a try/catch that:

    • Maps known user-actionable codes to { error: '...' } (existing duplicate email path — keep as-is)
    • For any other error: throw new Error('Failed to create auth user') but only AFTER logging the original error with console.error so it appears in Vercel logs with context
    • This ensures the dialog shows "Something went wrong. Please try again." toast rather than a blank crash
  3. Confirm the return shape. Log authServer.admin.createUser result shape in development to verify it returns { data, error } as assumed. If it throws rather than returning { error }, the try/catch approach replaces the current result-inspection pattern.

The neonAuthId extraction (data.user.id or data.id) should also be verified against the actual runtime shape and use optional chaining with a fallback throw.

@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed needs revision Review found issues that need fixing labels Jun 27, 2026
@b-at-neu
b-at-neu force-pushed the 239-allow-admins-to-create-user-accounts branch from d03dc93 to df756a0 Compare June 27, 2026 15:08
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 7

fixed R7-C1 · df756a0

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) ready for review PR ready for review agent labels Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Cycle 4 · approved

0 open — R3-C1 fully resolved; no Critical/Medium/Low/Nit remain

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Cycle 6 · needs revision

3 open — 1 🟠 Medium, 1 🟡 Low, 1 ⚪ Nit (see inline)

Comment thread prisma/actions/users.ts Outdated
Comment thread prisma/actions/users.ts Outdated
Comment thread prisma/actions/users.ts
@b-at-neu b-at-neu added needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed reviewing Review agent working (in-flight) needs revision Review found issues that need fixing labels Jun 27, 2026
b-at-neu and others added 8 commits June 27, 2026 17:12
…ma to

lib/constants, add createUser server action (admin-gated, calls
authServer.admin.createUser, upserts app User row in a transaction), and
wire a CreateUserDialog into the Users page header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace authServer.admin.createUser with a Prisma-only pre-invite approach:
make neonAuthId nullable, createUser inserts a pending row, and resolveRealUser
links it on the invited user's first OTP sign-in.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Call authServer.admin.createUser before creating the app row so the Neon Auth
account is pre-provisioned and the user can sign in via OTP immediately; revert
resolveRealUser to the original atomic upsert (race-safe, no nullable neonAuthId
needed); remove the make-neon-auth-id-nullable migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove authServer.admin.createUser (returns 401); createUser now
pre-creates a User row with email only. resolveRealUser() gains a
three-step lookup: linked fast-path, email-match link for pre-invited
users, then create with P2002 race guard. neonAuthId made nullable
to support pre-invite rows with no auth account yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 6

fixed R6-M1, R6-N1, R6-L1 · 87c7419

@b-at-neu b-at-neu removed the revising Revise agent working (in-flight) label Jun 27, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review — Cycle 8 · approved

0 open — R6-M1, R6-N1, R6-L1 all resolved; no Critical/Medium remain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Review passed, ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow Admins To Create User Accounts

1 participant