diff --git a/frontend/src/lib/utils/postgresConnectionString.test.ts b/frontend/src/lib/utils/postgresConnectionString.test.ts index 396e77d038..88f41792b7 100644 --- a/frontend/src/lib/utils/postgresConnectionString.test.ts +++ b/frontend/src/lib/utils/postgresConnectionString.test.ts @@ -35,6 +35,14 @@ describe('parsePostgresConnectionString', () => { expect(parsePostgresConnectionString('mysql://u:p@host/db')).toBeUndefined() expect(parsePostgresConnectionString('')).toBeUndefined() }) + + // Verified against psql: `postgres://role:p%40ss@host/db` authenticates as `p@ss`, and an + // unencoded `@` puts the rest of the password in libpq's host too. Reading these any other + // way would make the same string mean something here that it means nowhere else. + it('decodes percent escapes in credentials, as libpq does', () => { + expect(parsePostgresConnectionString('postgres://u:p%40ss@host/db')?.password).toBe('p@ss') + expect(parsePostgresConnectionString('postgres://u%40corp:p@host/db')?.user).toBe('u@corp') + }) }) // The wizard offers the same connection as a string or as fields and switches between them diff --git a/frontend/src/lib/utils/postgresConnectionString.ts b/frontend/src/lib/utils/postgresConnectionString.ts index 871a0111e2..558134979c 100644 --- a/frontend/src/lib/utils/postgresConnectionString.ts +++ b/frontend/src/lib/utils/postgresConnectionString.ts @@ -8,6 +8,11 @@ * The wizard offers the same connection as a string or as fields and lets the * user switch, so parse and compose have to be inverses: whatever one produces, * the other must read back unchanged. + * + * libpq is the arbiter of what a connection string means, so this follows it rather than + * RFC 3986 where they differ: credentials are split at the *first* `@` -- an unencoded one + * lands in the host for libpq too -- and percent escapes in them are decoded, so `p%40ss` + * authenticates as `p@ss`. */ const CONNECTION_STRING =