Fix invite user error by using admin client for tmp_users#1427
Conversation
The tmp_users table has an RLS policy that blocks all access ('Disable for all').
The invite function was using an authenticated client which is subject to RLS,
causing SELECT, INSERT, and UPDATE operations to fail with a 400 error.
Switch to using supabaseAdmin (service role client) for tmp_users operations
since authorization is already validated via validateInvite function.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe change replaces all temporary user table operations in the invite function from authenticated client to admin client, maintaining identical functionality while adjusting access permissions for tmp_users table interactions. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (7)supabase/functions/_backend/**/*.{ts,js}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
supabase/functions/**/*.{ts,js}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.{vue,ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
supabase/functions/**/*.ts📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx,vue}📄 CodeRabbit inference engine (AGENTS.md)
Files:
supabase/functions/_backend/**/*.ts📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (10)📚 Learning: 2026-01-10T04:55:25.264ZApplied to files:
📚 Learning: 2025-12-24T14:11:10.256ZApplied to files:
📚 Learning: 2026-01-10T04:55:25.264ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-25T11:22:19.594ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2026-01-10T04:55:25.264ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
🧬 Code graph analysis (1)supabase/functions/_backend/private/invite_new_user_to_org.ts (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (3)
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 |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
supabase/functions/_backend/private/invite_new_user_to_org.ts (1)
19-26: Strengthenorg_idvalidation to UUID format.Since
org_idis now used with the admin client (bypassing RLS), validating it as a proper UUID rather than just a non-empty string provides stronger protection against potential injection attacks.🔒 Proposed fix
const inviteUserSchema = z.object({ email: z.email(), - org_id: z.string().check(z.minLength(1)), + org_id: z.uuid(), invite_type: z.enum(['read', 'upload', 'write', 'admin', 'super_admin']), captcha_token: z.string().check(z.minLength(1)), first_name: z.string().check(z.minLength(1), z.regex(nameRegex, 'First name contains invalid characters')), last_name: z.string().check(z.minLength(1), z.regex(nameRegex, 'Last name contains invalid characters')), })Based on coding guidelines: "When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection."
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
supabase/functions/_backend/private/invite_new_user_to_org.ts
🧰 Additional context used
📓 Path-based instructions (7)
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintto lint Vue, TypeScript, and JavaScript files; usebun lint:fixto auto-fix issues
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use single quotes and no semicolons per @antfu/eslint-config rules
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
supabase/functions/**/*.ts: Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun lintor lint/format command before validating any backend or frontend task to ensure consistent formatting
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
supabase/functions/_backend/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
🧠 Learnings (10)
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : Never use the Supabase admin SDK with service key for user-facing APIs; always use client SDK with user authentication to enforce RLS policies. Admin SDK should only be used for internal operations (triggers, CRON jobs, etc.)
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/_backend/**/*.ts : Place core backend logic in supabase/functions/_backend/ with plugins, private, public, triggers, and utils subdirectories
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-25T11:22:19.594Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:19.594Z
Learning: In the 2FA enforcement implementation for supabase/migrations: When an org has enforcing_2fa=true, all users including super_admins must have 2FA enabled before accessing any org functions (including check_org_members_2fa_enabled); this is intentional behavior to ensure consistent security enforcement without exceptions for admins.
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2026-01-10T04:55:25.264Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-10T04:55:25.264Z
Learning: Applies to supabase/functions/**/*.ts : When admin access is unavoidable for a user-facing endpoint, sanitize all user inputs carefully to prevent PostgREST query injection
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/private/invite_new_user_to_org.ts
🧬 Code graph analysis (1)
supabase/functions/_backend/private/invite_new_user_to_org.ts (1)
supabase/functions/_backend/utils/supabase.ts (1)
supabaseAdmin(75-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (3)
supabase/functions/_backend/private/invite_new_user_to_org.ts (3)
10-10: LGTM!Import correctly adds
supabaseAdminto support the admin client usage for tmp_users operations.
116-161: Admin client operations for tmp_users are correctly implemented.The SELECT, UPDATE, and INSERT operations on
tmp_usersproperly use the admin client with validated inputs:
z.email()first_name/last_namevalidated with restrictivenameRegexinvite_typevalidated as enumorg_idused after org access is verified via authenticated client invalidateInviteThe conditional logic correctly handles both re-inviting cancelled users (with 3-hour cooldown) and creating new invitations.
113-114: The admin client usage is appropriate and necessary.The implementation correctly enforces authorization via the authenticated client first (validating user access to the org), then scopes the admin client operations exclusively to
tmp_users. All inputs are thoroughly validated with Zod schema before being passed to the admin client, preventing PostgREST query injection.The RLS policy on
tmp_usersis intentionally set to "Disable for all" by design requirement (documented insupabase/migrations/20250613034031_tmp_users_table.sql). This deliberate choice means the admin client is the only viable approach for managing temporary user invitations. The implementation correctly follows security guidelines by sanitizing all user inputs before admin operations.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5229190391
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Use admin client for tmp_users operations since RLS blocks all access on that table | ||
| const supabaseAdminClient = supabaseAdmin(c) | ||
|
|
There was a problem hiding this comment.
Enforce org admin rights before using service role
This handler now uses supabaseAdmin for all tmp_users writes, which bypasses RLS, but there is still no explicit check that the caller has admin+ rights for the org (the earlier validateInvite only validates access via orgs select). That means any authenticated member with read access to an org can invite new users or re‑activate invitations by calling this endpoint directly, even if they should not have invite permissions. The public members API explicitly checks for admin rights; this private route should enforce the same before performing service‑role inserts/updates.
Useful? React with 👍 / 👎.
|



Summary
Fixed the "Failed to invite user" error (400 status) that occurred when inviting users. The issue was that the
tmp_userstable has an RLS policy blocking all authenticated access, but the invite endpoint was using an authenticated Supabase client to perform SELECT, INSERT, and UPDATE operations on that table.Solution
Switched to using
supabaseAdmin(service role client) for alltmp_usersoperations. Authorization is already enforced by thevalidateInvitefunction which checks user membership and org access, so using the service role client is safe and necessary to bypass the RLS policy.Test plan
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.