Skip to content

AUTH-7 User server actions - #10

Open
wlenig wants to merge 7 commits into
mainfrom
AUTH-7
Open

AUTH-7 User server actions#10
wlenig wants to merge 7 commits into
mainfrom
AUTH-7

Conversation

@wlenig

@wlenig wlenig commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Created lib/users.ts with:

  • createUser(supabaseUserId) — create a user record linked to a Supabase auth user
  • getUser(id, includeEmail?) — get a user by SGA user ID, optionally include Supabase auth email
  • getUsers(filters?) — list all users, optional isAdmin filter
  • updateUser(id, data) — update user fields (e.g. toggle isAdmin)
  • deleteUser(id) — delete from users table and from auth.users via service role

@pataniaeli
pataniaeli self-requested a review July 22, 2026 19:57

@pataniaeli pataniaeli left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review written by a Claude agent.

Same framing note as the AUTH-8 review: a few of the items below only bite once these actions are reachable (exported and imported somewhere), so if there's a planned follow-up ticket for authorization before this gets wired into a client component, deferring those specific items is reasonable — I've separated them out so that's a visible, deliberate choice.

Must-fix regardless of sequencing:

  • updateUser's unfiltered data spread (see inline comment) — straightforward allowlist fix, independent of who's allowed to call it.
  • A likely merge collision with #3 (see inline comment on src/lib/supabase.ts) — worth resolving which Supabase client module is canonical before both land.
  • getUsers's where: filters needs field-level scoping, not just an auth check (see inline comment) — a valid authenticated caller in one project shouldn't be able to shape a filter that returns another project's users.
  • Raw Supabase Admin API error text (error.message, line 48) is currently returned to the caller — worth routing through something like #3's authErrorToQueryCode pattern instead of surfacing provider text directly.
  • deleteUser deletes the Supabase auth identity (line 94) before running the Prisma transaction (line 102) — if the transaction fails, session/membership rows survive pointing at an identity that no longer resolves. Might be worth swapping the order or wrapping both in a compensating flow.

Pending authz ticket:

  • None of the five exports currently check who's calling before reading/writing user data — most consequential combined with the updateUser issue above. Flagging so it's confirmed rather than assumed before this is wired up.

Worth its own ticket, not introduced by this PR: same RLS gap noted on the other open PRs — no policies found on the underlying tables.

(Edit: corrected a couple of line references above and a couple of inline comment anchors below that were off in my first pass — same content, right lines now.)

Comment thread src/lib/supabase.ts Outdated
@@ -0,0 +1,17 @@
import "server-only";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heads up on a collision with #3 (auth-11), independent of this PR's own scope: that branch adds a src/lib/supabase/ directory (server.ts, admin.ts, middleware.ts, etc.) exporting from an index.ts barrel. This file is src/lib/supabase.ts — same import specifier (@/lib/supabase), different shape. I tested merging both locally: git reports no conflict (different paths), but the file silently wins over the directory for that specifier, and tsc then fails on src/middleware.ts because updateSession can no longer be resolved — breaking session-cookie refresh app-wide with no build error to catch it before that point. Worth a quick sync with whoever owns #3 on which one is canonical before either merges — not a blocker on this PR in isolation, but a blocker on merging both.

Comment thread src/lib/users.ts Outdated
id: string,
data: { isAdmin?: boolean },
): Promise<User> {
return prisma.user.update({ where: { id }, data });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

data: { isAdmin?: boolean } is forwarded straight into prisma.user.update unfiltered. The TS type is compile-time only — Prisma will accept any field present on the object at runtime, including supabaseUserId (the only identity binding in this system, since it maps a User row to a specific Supabase auth account). Worth an explicit destructure (const { isAdmin } = data) regardless of what authorization ends up wrapping this, since even an authorized caller passing an unexpected extra field would silently be able to repoint identity fields on any row.

Comment thread src/lib/users.ts Outdated
isAdmin?: boolean;
}): Promise<User[]> {
return prisma.user.findMany({
where: filters,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Even once there's a valid authenticated caller here, where: filters passes the caller's object straight into Prisma's query builder with no scoping — e.g. { isAdmin: true } today, but the type doesn't stop a caller from sending a differently-shaped filter, and there's no restriction to the caller's own project(s) either way. This is a data-scoping issue that authentication alone won't fix — worth deciding whether that scoping is part of the same follow-up work or needs to land here.

@pataniaeli pataniaeli self-assigned this Aug 16, 2026
…llision

Addresses the "must-fix regardless of sequencing" findings from the AUTH-7
security review:

- updateUser now destructures isAdmin explicitly instead of spreading the
  caller-supplied data object into Prisma, closing a mass-assignment path
  that could otherwise write unexpected fields (e.g. supabaseUserId).
- getUsers now builds an explicit { isAdmin } where clause instead of
  passing the caller's filter object straight to Prisma's query builder.
- getUser no longer returns raw Supabase Admin API error text to the
  caller; details are logged server-side instead.
- deleteUser now runs the Prisma transaction before deleting the Supabase
  auth identity, so a failed transaction can't leave live session/
  membership rows pointing at an identity that no longer resolves.
- Renamed src/lib/supabase.ts to src/lib/supabase-admin.ts to avoid
  colliding with the src/lib/supabase/ directory added by #3 (auth-11),
  and added autoRefreshToken/persistSession: false plus clear env-var
  errors to match that module's admin client pattern.

Caller-identity/authorization checks are intentionally deferred — this
codebase has no merged session mechanism yet (the only one, #3, is still
open) — and are tracked via a TODO in users.ts pending that follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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