-
-
Notifications
You must be signed in to change notification settings - Fork 8
Fix Clerk-Supabase Auth Integration and Next.js 15 DB Error #708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| -- Create a function to extract the Clerk user ID from the JWT | ||
| CREATE OR REPLACE FUNCTION public.clerk_id() | ||
| RETURNS text AS $$ | ||
| SELECT (auth.jwt() ->> 'sub'); | ||
| $$ LANGUAGE sql STABLE; | ||
| --> statement-breakpoint | ||
|
|
||
| -- Create a function to check if a user matches the Clerk ID | ||
| CREATE OR REPLACE FUNCTION public.is_clerk_user(user_id uuid) | ||
| RETURNS boolean AS $$ | ||
| SELECT EXISTS ( | ||
| SELECT 1 FROM public.users | ||
| WHERE id = user_id AND clerk_user_id = public.clerk_id() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested fix: add a migration step before this function to create |
||
| ); | ||
| $$ LANGUAGE sql STABLE; | ||
| --> statement-breakpoint | ||
|
|
||
| -- Update RLS policies for users table | ||
| DROP POLICY IF EXISTS "Users can manage their own profile" ON public.users; | ||
| CREATE POLICY "Users can manage their own profile" ON public.users | ||
| FOR ALL USING ( | ||
| id = auth.uid() -- Original Supabase Auth | ||
| OR | ||
| clerk_user_id = public.clerk_id() -- Clerk Auth | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- Update RLS policies for chats table | ||
| DROP POLICY IF EXISTS "Users can manage their own chats" ON public.chats; | ||
| CREATE POLICY "Users can manage their own chats" ON public.chats | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() -- Original Supabase Auth | ||
| OR | ||
| public.is_clerk_user(user_id) -- Clerk Auth | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| DROP POLICY IF EXISTS "Users can insert their own chats" ON public.chats; | ||
| CREATE POLICY "Users can insert their own chats" ON public.chats | ||
| FOR INSERT WITH CHECK ( | ||
| user_id = auth.uid() -- Original Supabase Auth | ||
| OR | ||
| public.is_clerk_user(user_id) -- Clerk Auth | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| DROP POLICY IF EXISTS "Users can select chats they are a part of" ON public.chats; | ||
| CREATE POLICY "Users can select chats they are a part of" ON public.chats | ||
| FOR SELECT USING ( | ||
| user_id = auth.uid() -- Original Supabase Auth | ||
| OR | ||
| public.is_clerk_user(user_id) -- Clerk Auth | ||
| OR | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants | ||
| WHERE chat_id = chats.id AND (user_id = auth.uid() OR public.is_clerk_user(user_id)) | ||
| ) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- Messages | ||
| DROP POLICY IF EXISTS "messages_owner_participant_access" ON public.messages; | ||
| CREATE POLICY "messages_owner_participant_access" ON public.messages | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() | ||
| OR | ||
| public.is_clerk_user(user_id) | ||
| OR | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chats | ||
| WHERE chats.id = messages.chat_id AND (chats.user_id = auth.uid() OR public.is_clerk_user(chats.user_id)) | ||
| ) | ||
| OR | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants | ||
| WHERE chat_participants.chat_id = messages.chat_id AND (chat_participants.user_id = auth.uid() OR public.is_clerk_user(chat_participants.user_id)) | ||
| ) | ||
| ); | ||
|
Comment on lines
+63
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Split message read and write policies. This 🤖 Prompt for AI Agents |
||
| --> statement-breakpoint | ||
|
|
||
| -- Calendar Notes | ||
| DROP POLICY IF EXISTS "Users can manage their own calendar notes" ON public.calendar_notes; | ||
| CREATE POLICY "Users can manage their own calendar notes" ON public.calendar_notes | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() | ||
| OR | ||
| public.is_clerk_user(user_id) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- System Prompts | ||
| DROP POLICY IF EXISTS "Users can manage their own system prompts" ON public.system_prompts; | ||
| CREATE POLICY "Users can manage their own system prompts" ON public.system_prompts | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() | ||
| OR | ||
| public.is_clerk_user(user_id) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- Locations | ||
| DROP POLICY IF EXISTS "Users can manage their own locations" ON public.locations; | ||
| CREATE POLICY "Users can manage their own locations" ON public.locations | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() | ||
| OR | ||
| public.is_clerk_user(user_id) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- Visualizations | ||
| DROP POLICY IF EXISTS "Users can manage their own visualizations" ON public.visualizations; | ||
| CREATE POLICY "Users can manage their own visualizations" ON public.visualizations | ||
| FOR ALL USING ( | ||
| user_id = auth.uid() | ||
| OR | ||
| public.is_clerk_user(user_id) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| -- Chat Participants | ||
| DROP POLICY IF EXISTS "Only owners can delete participants" ON public.chat_participants; | ||
| CREATE POLICY "Only owners can delete participants" ON public.chat_participants | ||
| FOR DELETE USING ( | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants cp | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This policy queries Suggested fix: move the ownership/membership lookup into a hardened |
||
| WHERE cp.chat_id = chat_participants.chat_id | ||
| AND (cp.user_id = auth.uid() OR public.is_clerk_user(cp.user_id)) | ||
| AND cp.role = 'owner' | ||
| ) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| DROP POLICY IF EXISTS "Only owners can insert participants" ON public.chat_participants; | ||
| CREATE POLICY "Only owners can insert participants" ON public.chat_participants | ||
| FOR INSERT WITH CHECK ( | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants cp | ||
| WHERE cp.chat_id = chat_participants.chat_id | ||
| AND (cp.user_id = auth.uid() OR public.is_clerk_user(cp.user_id)) | ||
| AND cp.role = 'owner' | ||
| ) | ||
| ); | ||
|
Comment on lines
+134
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== migration excerpt ==\n'
nl -ba drizzle/migrations/0003_add_clerk_auth_helpers.sql | sed -n '110,165p'
printf '\n== search related schema/policies ==\n'
rg -n "chat_participants|CREATE POLICY|public\.chats|is_clerk_user|owner" drizzle -g '!**/node_modules/**'Repository: QueueLab/QCX Length of output: 215 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== migration excerpt around chat tables/helpers ==\n'
nl -ba drizzle/migrations/0003_add_clerk_auth_helpers.sql | sed -n '1,220p'
printf '\n== file list for migration/schema refs ==\n'
git ls-files drizzle | sed -n '1,120p'Repository: QueueLab/QCX Length of output: 242 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== migration excerpt ==\n'
awk 'NR>=110 && NR<=165 {printf "%d:%s\n", NR, $0}' drizzle/migrations/0003_add_clerk_auth_helpers.sql
printf '\n== surrounding file search ==\n'
rg -n "chat_participants|CREATE POLICY|public\.chats|is_clerk_user|owner" drizzle
printf '\n== file list under drizzle ==\n'
git ls-files drizzle | head -200Repository: QueueLab/QCX Length of output: 12098 Allow bootstrapping the first owner participant. This INSERT policy requires an existing owner row in 🧰 Tools🪛 SQLFluff (4.2.2)[error] 134-134: Do not use special characters in identifiers. (RF05) [error] 135-135: Do not use special characters in identifiers. (RF05) 🤖 Prompt for AI Agents |
||
| --> statement-breakpoint | ||
|
|
||
| DROP POLICY IF EXISTS "Only owners can update participants" ON public.chat_participants; | ||
| CREATE POLICY "Only owners can update participants" ON public.chat_participants | ||
| FOR UPDATE USING ( | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants cp | ||
| WHERE cp.chat_id = chat_participants.chat_id | ||
| AND (cp.user_id = auth.uid() OR public.is_clerk_user(cp.user_id)) | ||
| AND cp.role = 'owner' | ||
| ) | ||
| ); | ||
| --> statement-breakpoint | ||
|
|
||
| DROP POLICY IF EXISTS "Participants can view other participants" ON public.chat_participants; | ||
| CREATE POLICY "Participants can view other participants" ON public.chat_participants | ||
| FOR SELECT USING ( | ||
| EXISTS ( | ||
| SELECT 1 FROM public.chat_participants cp | ||
| WHERE cp.chat_id = chat_participants.chat_id | ||
| AND (cp.user_id = auth.uid() OR public.is_clerk_user(cp.user_id)) | ||
| ) | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,34 @@ | ||
| import { drizzle } from 'drizzle-orm/node-postgres'; | ||
| import { Pool, type PoolConfig } from 'pg'; // Uses Pool from pg, import PoolConfig | ||
| import * as dotenv from 'dotenv'; | ||
| import { Pool, type PoolConfig } from 'pg'; | ||
| import { parse } from 'pg-connection-string'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify pg-connection-string is declared in package manifests.
fd -a '^package\.json$' -x sh -c '
for f do
echo "== $f"
jq -r '"'"'{
dependencies: .dependencies["pg-connection-string"],
devDependencies: .devDependencies["pg-connection-string"],
peerDependencies: .peerDependencies["pg-connection-string"]
}'"'"' "$f"
done
' sh {}Repository: QueueLab/QCX Length of output: 267 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "## manifests"
fd -a '^package\.json$' . | sort
echo
echo "## declaration scan"
for f in $(fd -a '^package\.json$' . | sort); do
if jq -e '.dependencies["pg-connection-string"] or .devDependencies["pg-connection-string"] or .peerDependencies["pg-connection-string"]' "$f" >/dev/null; then
echo "FOUND in $f"
jq -r '{file:"'"$f"'", dep:.dependencies["pg-connection-string"], dev:.devDependencies["pg-connection-string"], peer:.peerDependencies["pg-connection-string"]}' "$f"
fi
done
echo
echo "## import scan"
rg -n "pg-connection-string|from 'pg-connection-string'|from \"pg-connection-string\"" lib package.json . -g '!node_modules' -g '!dist' -g '!build' || true
echo
echo "## relevant files"
for f in lib/db/index.ts lib/db/migrate.ts; do
if [ -f "$f" ]; then
echo "== $f =="
cat -n "$f" | sed -n '1,120p'
fi
doneRepository: QueueLab/QCX Length of output: 5190 Declare 🤖 Prompt for AI Agents |
||
| import * as schema from './schema'; | ||
|
|
||
| dotenv.config({ path: '.env.local' }); | ||
|
|
||
| // In production/build environments, we might not have DATABASE_URL immediately available | ||
| // especially during Next.js static optimization phases. | ||
| if (!process.env.DATABASE_URL && process.env.NODE_ENV === 'production') { | ||
| console.warn('DATABASE_URL environment variable is not set. Database features will be unavailable.'); | ||
| } | ||
|
|
||
| const connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/postgres'; | ||
|
Comment on lines
8
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git ls-files 'lib/db/index.ts' 'lib/db/**' 'package.json' 'next.config.*' 'app/**' 'pages/**' 'src/**' | sed -n '1,200p'
echo '--- lib/db/index.ts ---'
cat -n lib/db/index.ts | sed -n '1,120p'
echo '--- search DATABASE_URL usage ---'
rg -n "DATABASE_URL|postgres://localhost:5432/postgres|connectionString|db/index" lib src app pages . --glob '!node_modules' --glob '!dist' --glob '!build'
echo '--- package.json ---'
cat -n package.json | sed -n '1,220p'Repository: QueueLab/QCX Length of output: 5459 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- next.config.mjs ---'
cat -n next.config.mjs | sed -n '1,220p'
echo '--- package.json ---'
cat -n package.json | sed -n '1,240p'
echo '--- db import sites ---'
rg -n "from '.*lib/db|from \" .*lib/db|from './db|from '../db|from '`@/lib/db`|from 'lib/db|from \"lib/db" app lib --glob '!node_modules' --glob '!dist' --glob '!build'
echo '--- app/api/chat/route.ts ---'
cat -n app/api/chat/route.ts | sed -n '1,220p'
echo '--- app/actions.tsx ---'
cat -n app/actions.tsx | sed -n '1,260p'Repository: QueueLab/QCX Length of output: 22015 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- lib/actions/chat.ts ---'
cat -n lib/actions/chat.ts | sed -n '1,260p'
echo '--- lib/actions/chat-db.ts ---'
cat -n lib/actions/chat-db.ts | sed -n '1,260p'
echo '--- lib/auth/get-current-user.ts ---'
cat -n lib/auth/get-current-user.ts | sed -n '1,220p'
echo '--- lib/actions/users.ts ---'
cat -n lib/actions/users.ts | sed -n '1,220p'
echo '--- lib/actions/system-prompt-db.ts ---'
cat -n lib/actions/system-prompt-db.ts | sed -n '1,220p'
echo '--- lib/actions/calendar.ts ---'
cat -n lib/actions/calendar.ts | sed -n '1,240p'Repository: QueueLab/QCX Length of output: 37522 Do not let production runtime fall back to localhost
🤖 Prompt for AI Agents |
||
|
|
||
| // Manually parse the connection string to avoid problematic internal URL parsing in 'pg' driver | ||
| // which can trigger "TypeError: Cannot read properties of undefined (reading 'searchParams')" | ||
| // especially in Next.js 15 environments where global Request/URL objects are sensitive. | ||
| const parsedConfig = parse(connectionString); | ||
|
|
||
| // Construct pool configuration using parsed values instead of the raw connection string | ||
| // to bypass the internal parser in the 'pg' library. | ||
| const poolConfig: PoolConfig = { | ||
| connectionString: process.env.DATABASE_URL || 'postgres://localhost:5432/postgres', | ||
| host: parsedConfig.host || undefined, | ||
| port: parsedConfig.port ? parseInt(parsedConfig.port, 10) : undefined, | ||
| user: parsedConfig.user || undefined, | ||
| password: parsedConfig.password || undefined, | ||
| database: parsedConfig.database || undefined, | ||
| ssl: parsedConfig.ssl as any, | ||
| }; | ||
|
|
||
| // Conditionally apply SSL for Supabase URLs | ||
| if (process.env.DATABASE_URL && process.env.DATABASE_URL.includes('supabase.co')) { | ||
| // Conditionally apply SSL for Supabase URLs if not already specified in the connection string | ||
| if (connectionString.includes('supabase.co') && !poolConfig.ssl) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline lib/db/index.ts --view expanded || true
printf '\n== Relevant lines in lib/db/index.ts ==\n'
cat -n lib/db/index.ts | sed -n '1,120p'
printf '\n== Search for ssl parsing / poolConfig.ssl usage ==\n'
rg -n "poolConfig\.ssl|ssl\s*===" lib/db/index.ts lib -g '!**/node_modules/**' || trueRepository: QueueLab/QCX Length of output: 2836 🌐 Web query:
💡 Result: In the pg-connection-string library, the use of ssl=false in a connection string can lead to unexpected behavior because the library parses the "false" value as a string rather than a boolean [1]. Since many database clients (like knex) expect the ssl property to be a boolean, passing ssl=false can result in the library interpreting it as truthy, sometimes causing the client to incorrectly attempt an SSL connection [1]. The authoritative and recommended way to disable SSL in a connection string when using pg-connection-string is to use the sslmode parameter [2]. By setting sslmode=disable in your connection string (e.g., postgres://user:password@host/db?sslmode=disable), the library will correctly interpret this and set the ssl configuration to false [3][4]. This approach aligns with standard PostgreSQL libpq connection URI behavior, whereas ssl=false is not a standard libpq parameter and can cause errors or inconsistencies [2]. Summary of recommendations: 1. Avoid using ssl=false in connection strings [2]. 2. Use sslmode=disable to explicitly disable SSL [3][2]. 3. Ensure your client library supports the parsed object output from pg-connection-string, as some drivers may require explicit configuration if they do not automatically map sslmode to the appropriate connection settings [1][5]. Citations:
🌐 Web query:
💡 Result: In the pg-connection-string library, setting sslmode=disable in a connection string correctly parses the ssl property to the boolean false [1][2][3]. When parsing a PostgreSQL connection string, the library interprets various query parameters to configure the SSL settings [2][4]. Specifically, for the sslmode parameter: - When uselibpqcompat is not set: - sslmode=disable sets the ssl property to false [3][5]. - sslmode=no-verify sets the ssl property to { rejectUnauthorized: false } [3][4]. - Other modes (prefer, require, verify-ca, verify-full) set the ssl property to true [3][6]. - When uselibpqcompat=true is used: - sslmode=disable also sets the ssl property to false [2][7]. Additionally, you can explicitly set the ssl status using the ssl query parameter directly (e.g., ssl=0 or ssl=false), which also forces the ssl property to be false [2][8]. Citations:
Respect explicit SSL settings from the connection string (lib/db/index.ts:31). 🤖 Prompt for AI Agents |
||
| poolConfig.ssl = { | ||
| rejectUnauthorized: false, | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { drizzle } from 'drizzle-orm/node-postgres'; | ||
| import { migrate } from 'drizzle-orm/node-postgres/migrator'; | ||
| import { Pool } from 'pg'; | ||
| import { parse } from 'pg-connection-string'; | ||
| import * as dotenv from 'dotenv'; | ||
|
|
||
| dotenv.config({ path: '.env.local' }); | ||
|
|
@@ -10,12 +11,17 @@ async function runMigrations() { | |
| throw new Error('DATABASE_URL environment variable is not set for migrations'); | ||
| } | ||
|
|
||
| const connectionString = process.env.DATABASE_URL; | ||
| const parsedConfig = parse(connectionString); | ||
|
|
||
| const pool = new Pool({ | ||
| connectionString: process.env.DATABASE_URL, | ||
| ssl: { | ||
| rejectUnauthorized: false, // Ensure this is appropriate for your Supabase connection | ||
| }, | ||
| // max: 1, // Optional: restrict to 1 connection for migration | ||
| host: parsedConfig.host || undefined, | ||
| port: parsedConfig.port ? parseInt(parsedConfig.port, 10) : undefined, | ||
| user: parsedConfig.user || undefined, | ||
| password: parsedConfig.password || undefined, | ||
| database: parsedConfig.database || undefined, | ||
| ssl: connectionString.includes('supabase.co') ? { rejectUnauthorized: false } : undefined, | ||
| max: 1, | ||
|
Comment on lines
+15
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git ls-files 'lib/db/*' 'lib/db/migrate.ts' 'lib/db/index.ts'
printf '\n--- migrate.ts ---\n'
cat -n lib/db/migrate.ts
printf '\n--- index.ts ---\n'
cat -n lib/db/index.ts
printf '\n--- search for parse(...) and ssl handling ---\n'
rg -n "parse\\(|sslmode|rejectUnauthorized|ssl:" lib/dbRepository: QueueLab/QCX Length of output: 4659 Preserve parsed SSL settings in migrations. 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| const db = drizzle(pool); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: QueueLab/QCX
Length of output: 5840
🏁 Script executed:
Repository: QueueLab/QCX
Length of output: 6712
Avoid raw
auth.uid()in the mixed Clerk/Supabase policies (drizzle/migrations/0003_add_clerk_auth_helpers.sql:20-166). Clerksubvalues are text IDs, so these checks can fail for Clerk requests before thepublic.is_clerk_user(...)branch runs. Replace them with a safe UUID helper and use it throughout this migration.🤖 Prompt for AI Agents