Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions drizzle/migrations/0003_add_clerk_auth_helpers.sql
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;
Comment on lines +4 to +5

Copy link
Copy Markdown
Contributor

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:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files | rg 'drizzle/migrations/0003_add_clerk_auth_helpers\.sql|drizzle/migrations/.*\.sql$|supabase|auth|rls|policy' || true

printf '\n== Search auth.uid / clerk_id / auth.jwt ==\n'
rg -n "auth\.uid\(|clerk_id\(|auth\.jwt\(" drizzle supabase . --glob '*.sql' --glob '*.ts' --glob '*.js' --glob '*.mjs' --glob '*.cjs' || true

printf '\n== Read target migration ==\n'
if [ -f drizzle/migrations/0003_add_clerk_auth_helpers.sql ]; then
  nl -ba drizzle/migrations/0003_add_clerk_auth_helpers.sql | sed -n '1,220p'
fi

Repository: QueueLab/QCX

Length of output: 5840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' drizzle/migrations/0003_add_clerk_auth_helpers.sql | cat -n

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). Clerk sub values are text IDs, so these checks can fail for Clerk requests before the public.is_clerk_user(...) branch runs. Replace them with a safe UUID helper and use it throughout this migration.

🤖 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 `@drizzle/migrations/0003_add_clerk_auth_helpers.sql` around lines 4 - 5, The
mixed Clerk/Supabase policy checks are using raw auth.uid() comparisons, which
can fail for Clerk text-based subject IDs before the public.is_clerk_user(...)
branch is evaluated. Update the helper in this migration to use a safe
UUID-returning auth function and replace every auth.uid() usage in the affected
policies/functions with that helper so Clerk requests are handled consistently.

--> 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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

is_clerk_user depends on users.clerk_user_id, but this migration set does not include a prior ALTER TABLE users ADD COLUMN clerk_user_id .... On fresh/reset databases, this reference can fail during migration and block deploys.

Suggested fix: add a migration step before this function to create clerk_user_id (and the intended uniqueness constraint/index), or ensure a guaranteed earlier migration provides it.

);
$$ 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Split message read and write policies.

This FOR ALL policy lets any chat participant satisfy INSERT/UPDATE/DELETE checks through the participant EXISTS, regardless of messages.user_id. A participant can create or mutate messages attributed to another user. Keep participant access for SELECT, but require messages.user_id to match the current Supabase/Clerk user for INSERT/UPDATE/DELETE.

🤖 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 `@drizzle/migrations/0003_add_clerk_auth_helpers.sql` around lines 63 - 78,
Split the current messages_owner_participant_access policy in the messages
policy definition so participant-based access is only used for reading, not
writing. The existing FOR ALL policy in the messages policy block allows the
chat_participants EXISTS branch to satisfy INSERT/UPDATE/DELETE checks even when
messages.user_id does not match the current user; keep that participant logic
for SELECT access, but create separate write rules that require messages.user_id
to equal auth.uid() or the Clerk-backed user check before allowing
INSERT/UPDATE/DELETE.

--> 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This policy queries public.chat_participants from inside a policy on public.chat_participants. PostgreSQL applies RLS to that subquery too, which can recurse and fail with infinite recursion detected in policy for relation "chat_participants" (same self-reference pattern appears again in the INSERT/UPDATE/SELECT policies below).

Suggested fix: move the ownership/membership lookup into a hardened SECURITY DEFINER helper owned by a role that bypasses RLS (or otherwise avoid self-referencing this table inside its own policies).

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 -200

Repository: QueueLab/QCX

Length of output: 12098


Allow bootstrapping the first owner participant. This INSERT policy requires an existing owner row in public.chat_participants, so a new chat cannot add its initial owner. Add a bootstrap branch that allows the creator from public.chats.user_id.

🧰 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@drizzle/migrations/0003_add_clerk_auth_helpers.sql` around lines 134 - 143,
The INSERT policy for chat participants currently depends on an existing owner
row in public.chat_participants, which blocks creating the first owner. Update
the policy in the chat_participants migration to special-case the bootstrap
insert by allowing the creator identified by public.chats.user_id to insert the
initial owner participant for that chat. Keep the existing owner-check logic for
subsequent inserts, and reference the policy definition around the "Only owners
can insert participants" policy when making the change.

--> 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))
)
);
9 changes: 8 additions & 1 deletion drizzle/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
"when": 1782395315062,
"tag": "0002_lively_black_widow",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1783500279587,
"tag": "0003_add_clerk_auth_helpers",
"breakpoints": true
}
]
}
}
14 changes: 11 additions & 3 deletions lib/auth/get-current-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ export async function getClerkUserIdOnServer(): Promise<string | null> {
if (AUTH_DISABLED_FLAG) {
return MOCK_USER_ID;
}
const { userId } = await auth();
return userId;
try {
const { userId } = await auth();
return userId;
} catch (error) {
console.error('[Auth] Error getting Clerk user ID:', error);
return null;
}
}

/**
Expand Down Expand Up @@ -47,7 +52,10 @@ export async function resolveClerkUserToDbUser(clerkUserId: string): Promise<str

// 2. If not found, fetch user details from Clerk and create a new record
const clerkUser = await currentUser();
if (!clerkUser) return null;
if (!clerkUser) {
console.warn('[Auth] No Clerk user found for ID:', clerkUserId);
return null;
}

const email = clerkUser.emailAddresses[0]?.emailAddress;

Expand Down
26 changes: 19 additions & 7 deletions lib/db/index.ts
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';

Copy link
Copy Markdown
Contributor

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:

#!/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
done

Repository: QueueLab/QCX

Length of output: 5190


Declare pg-connection-string directly. lib/db/index.ts and lib/db/migrate.ts import it at runtime, but it is not listed in the root package.json; relying on pg’s transitive copy can break under pnpm, PnP, or other non-hoisted installs. Add it to production dependencies.

🤖 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 `@lib/db/index.ts` at line 3, Add pg-connection-string to the root production
dependencies because lib/db/index.ts and lib/db/migrate.ts import it directly at
runtime. Update package.json so the dependency is declared explicitly, and keep
the existing imports using parse from pg-connection-string unchanged.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

lib/db/index.ts is imported by server actions and API routes, so if DATABASE_URL is missing in production this code will still create a pool pointed at postgres://localhost:5432/postgres and run real queries there. Make production fail closed instead of silently targeting a local/stale database.

🤖 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 `@lib/db/index.ts` around lines 8 - 12, In lib/db/index.ts, the DATABASE_URL
handling currently falls back to a localhost connection even when NODE_ENV is
production, which can let server actions and API routes run real queries against
the wrong database. Update the module-level connection setup around
connectionString so production fails closed: when process.env.NODE_ENV is
production and DATABASE_URL is missing, stop initialization by throwing an error
(or otherwise preventing pool creation) instead of defaulting to
postgres://localhost:5432/postgres. Keep the existing warning behavior only for
non-production environments, and ensure the fix is applied in the db
initialization path that creates the pool.


// 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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/**' || true

Repository: QueueLab/QCX

Length of output: 2836


🌐 Web query:

pg-connection-string parse ssl false undefined sslmode disable source

💡 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:

pg-connection-string README sslmode disable parse output ssl boolean false

💡 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).
poolConfig.ssl can be false for sslmode=disable or ssl=false, so !poolConfig.ssl treats an explicit opt-out as unset and overwrites it for Supabase URLs. Check for undefined instead.

🤖 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 `@lib/db/index.ts` at line 31, The SSL handling in the database connection
setup is treating an explicit false as if SSL was unset. In lib/db/index.ts
within the connection logic that checks Supabase URLs, update the condition
around poolConfig.ssl so it only applies the Supabase SSL override when ssl is
undefined, not when it was explicitly set to false. Keep the existing behavior
in the connectionString-based branch and preserve the caller’s sslmode/ssl=false
intent.

poolConfig.ssl = {
rejectUnauthorized: false,
};
Expand Down
16 changes: 11 additions & 5 deletions lib/db/migrate.ts
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' });
Expand All @@ -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

Copy link
Copy Markdown
Contributor

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:

#!/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/db

Repository: QueueLab/QCX

Length of output: 4659


Preserve parsed SSL settings in migrations. lib/db/migrate.ts drops parsedConfig.ssl, so URLs that rely on sslmode=require, certificates, or sslmode=no-verify can behave differently from lib/db/index.ts and fail during migrations. Use parsedConfig.ssl first, then apply the Supabase fallback only when SSL isn’t set.

🤖 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 `@lib/db/migrate.ts` around lines 15 - 24, The migration pool setup in
migrate.ts is ignoring parsedConfig.ssl, which can make migration connections
behave differently from the main database path. Update the Pool configuration in
the migration logic to prefer parsedConfig.ssl from parse(connectionString), and
only apply the Supabase ssl fallback when parsedConfig.ssl is not present; use
the existing migrate.ts Pool construction and the same connectionString parsing
flow to keep SSL behavior consistent with lib/db/index.ts.

});

const db = drizzle(pool);
Expand Down