From 50709e252d3014ca92ac92276473341735ead5f2 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Mon, 11 May 2026 17:32:52 -0700 Subject: [PATCH] fix(link): refresh masked-password URLs on re-link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-0.1.72 cloud `link --auth` wrote DATABASE_URL with a literal placeholder password (the cloud `/database-connection-string` endpoint masks the password as `********`; the unmask via the separate `/database-password` endpoint only landed in #108 / shipped in 0.1.72). `refreshStaleEnvDefaults` only refreshes when the user's value matches the manifest's literal default. Users who linked under an older CLI have `postgresql://postgres:********@cloud-host/db?sslmode=require` in their .env.local — that doesn't match the manifest's `localhost:5432` default, so re-linking under 0.1.72+ never replaces it. BA's pg pool then fails with "password authentication failed for user 'postgres'" on every sign-up attempt. The masked password is never a valid Postgres credential — `:****@` patterns can be safely treated as stale and replaced from the platform value alongside the existing "matches the manifest default" branch. Adds a unit test covering the masked-URL refresh case. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/auth-providers/apply.test.ts | 17 +++++++++++++++++ src/auth-providers/apply.ts | 14 +++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/auth-providers/apply.test.ts b/src/auth-providers/apply.test.ts index 74024fd..4faa142 100644 --- a/src/auth-providers/apply.test.ts +++ b/src/auth-providers/apply.test.ts @@ -100,6 +100,23 @@ describe('refreshStaleEnvDefaults', () => { expect(updated).toBe(existing); }); + it('refreshes a masked-password URL even if it differs from the manifest default', () => { + // Pre-0.1.72 cloud links wrote `postgresql://postgres:********@...` + // (placeholder password) to .env.local. Re-linking under 0.1.72+ should + // replace that with the real spliced password from /database-password. + const existing = 'DATABASE_URL=postgresql://postgres:********@m8s5kmam.us-east.database.insforge.app:5432/insforge?sslmode=require\n'; + const defaults = new Map([ + ['DATABASE_URL', 'postgresql://postgres:postgres@127.0.0.1:5432/insforge'], + ]); + const platform = new Map([ + ['DATABASE_URL', 'postgresql://postgres:realsecret@m8s5kmam.us-east.database.insforge.app:5432/insforge?sslmode=require'], + ]); + const { updated, refreshed } = refreshStaleEnvDefaults(existing, defaults, platform); + expect(refreshed).toEqual(['DATABASE_URL']); + expect(updated).toContain('postgres:realsecret@'); + expect(updated).not.toContain('********'); + }); + it('handles multiple keys, refreshing only the stale ones', () => { const existing = [ 'DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/insforge', diff --git a/src/auth-providers/apply.ts b/src/auth-providers/apply.ts index 68f7545..dba64c5 100644 --- a/src/auth-providers/apply.ts +++ b/src/auth-providers/apply.ts @@ -113,7 +113,16 @@ export function extractEnvPairs(content: string): Map { // value matches the manifest's literal default AND the platform now has a // real value (e.g., cloud DATABASE_URL), overwrite the line. User-customized // values (anything that differs from the manifest default) are preserved. +// +// One extra case beyond "matches the default": the cloud's +// `database-connection-string` endpoint used to return a placeholder URL +// like `postgresql://postgres:********@host/db?sslmode=require` before +// 0.1.72 spliced the real password in. Users linked under that older CLI +// still have the masked URL in their .env.local. The masked value can +// never authenticate to Postgres, so it's clearly stale — detect any +// `:****@` (one or more `*`) password and refresh from the platform. // Exported for unit testing. +const MASKED_PASSWORD_PATTERN = /:\*+@/; export function refreshStaleEnvDefaults( existing: string, manifestDefaults: Map, @@ -127,7 +136,10 @@ export function refreshStaleEnvDefaults( const userValue = m[2]; const def = manifestDefaults.get(key); const real = platformValues.get(key); - if (def !== undefined && real !== undefined && userValue === def && real !== def) { + if (def === undefined || real === undefined || real === def) return line; + const matchesDefault = userValue === def; + const isMaskedPassword = MASKED_PASSWORD_PATTERN.test(userValue); + if (matchesDefault || isMaskedPassword) { refreshed.push(key); return `${key}=${real}`; }