mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
fix(connection-manager): strip .<project-ref> suffix from username when deriving direct URL
`deriveDirectUrl()` correctly rewrites the host (`aws-0-us-east-1.pooler.supabase.com`
→ `db.abcxyz.supabase.co`) but preserves the full pooler-form username
(`postgres.abcxyz`). Supabase direct connections expect a bare `postgres`
username — Supavisor uses the `.<ref>` suffix for tenant routing, but it's
not a real database user. The auto-derived URL therefore fails to authenticate
even with the correct password:
password authentication failed for user "postgres.abcxyz"
Strip the suffix to `postgres` whenever the project-ref was successfully
extracted (same condition that triggers the host rewrite). The non-pooler
username branch is unaffected — preserved as-is to keep the port-only
fallback case working.
Hit while exercising v0.30.1's dual-pool routing on a real Supabase brain;
the kill switch (`GBRAIN_DISABLE_DIRECT_POOL=1`) papered over it locally
but every Supabase user with a stock pooler URL would silently fall through
to single-pool until the user-supplied a `GBRAIN_DIRECT_DATABASE_URL`
override. With this fix, dual-pool works out of the box for the canonical
Supabase shape.
Test additions:
- 1 case asserting bare `postgres:secret@` in the derived URL when
project-ref is parseable from the pooler URL (the new behavior)
- extends the existing "falls back to port-only" case with an
assertion that non-pooler usernames are preserved (unchanged behavior)
`bun run typecheck` clean. `deriveDirectUrl` test block passes 5/5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
dffb607ef7
commit
ddf2c6a9a0
@@ -142,16 +142,22 @@ export function deriveDirectUrl(url: string): string | null {
|
||||
const decodedUser = decodeURIComponent(user);
|
||||
const refMatch = decodedUser.match(/^postgres\.([a-z0-9]+)$/i);
|
||||
let directHost = hostname;
|
||||
let directUser = parsed.username;
|
||||
if (refMatch && refMatch[1] && isPoolerHost) {
|
||||
directHost = `db.${refMatch[1]}.supabase.co`;
|
||||
// Supabase direct connections use bare `postgres`; the `postgres.<ref>`
|
||||
// form is pooler-only (Supavisor uses the suffix for tenant routing).
|
||||
// Without this strip, direct auth fails with `password authentication
|
||||
// failed for user "postgres.<ref>"` even though the password is correct.
|
||||
directUser = 'postgres';
|
||||
}
|
||||
// Compose direct URL by swapping host + port. Preserve auth, db, query.
|
||||
parsed.hostname = directHost;
|
||||
parsed.port = '5432';
|
||||
// Reconstruct with the original scheme.
|
||||
const scheme = url.match(/^postgres(?:ql)?:\/\//i)?.[0] ?? 'postgres://';
|
||||
const auth = parsed.username
|
||||
? `${parsed.username}${parsed.password ? `:${parsed.password}` : ''}@`
|
||||
const auth = directUser
|
||||
? `${directUser}${parsed.password ? `:${parsed.password}` : ''}@`
|
||||
: '';
|
||||
const search = parsed.search ?? '';
|
||||
const path = parsed.pathname ?? '';
|
||||
|
||||
@@ -44,6 +44,18 @@ describe('deriveDirectUrl', () => {
|
||||
expect(direct).toContain(':secret@'); // creds preserved
|
||||
});
|
||||
|
||||
test('strips .<project-ref> suffix from username when going pooler→direct', () => {
|
||||
// Supabase direct connections require bare `postgres`; the `postgres.<ref>`
|
||||
// form is pooler-only (Supavisor uses the suffix for tenant routing).
|
||||
// Without the strip, direct auth fails with "password authentication
|
||||
// failed for user postgres.<ref>" even with the correct password.
|
||||
const direct = deriveDirectUrl(
|
||||
'postgresql://postgres.abcxyz:secret@aws-0-us-east-1.pooler.supabase.com:6543/postgres'
|
||||
);
|
||||
expect(direct).toContain('postgres:secret@'); // bare username
|
||||
expect(direct).not.toContain('postgres.abcxyz:secret@'); // no pooler suffix
|
||||
});
|
||||
|
||||
test('falls back to port-only swap when project-ref unparseable', () => {
|
||||
const direct = deriveDirectUrl(
|
||||
'postgresql://customuser:secret@some.pooler.supabase.com:6543/db'
|
||||
@@ -51,6 +63,7 @@ describe('deriveDirectUrl', () => {
|
||||
expect(direct).toBeTruthy();
|
||||
expect(direct).toContain(':5432');
|
||||
expect(direct).toContain('some.pooler.supabase.com'); // host preserved
|
||||
expect(direct).toContain('customuser:secret@'); // non-pooler username preserved
|
||||
});
|
||||
|
||||
test('returns null for non-pooler URL', () => {
|
||||
|
||||
Reference in New Issue
Block a user