| 1 | import type * as PgClient from "@effect/sql-pg/PgClient"; |
| 2 | import * as Redacted from "effect/Redacted"; |
| 3 | |
| 4 | type PgSsl = PgClient.PgPoolConfig["ssl"]; |
| 5 | |
| 6 | /** |
| 7 | * `sslmode` values `@effect/sql-pg` (≥ 4.0.0-rc.113, still as of rc.115) |
| 8 | * refuses unless `ssl` is set explicitly: `sslmode "prefer" is not |
| 9 | * supported: set ssl explicitly to true or false`. node-postgres treated both |
| 10 | * as "TLS on" (aliases of `verify-full`), and URLs carry them by default in |
| 11 | * places — Hyperdrive's local `dev` origin passthrough hands the worker |
| 12 | * `?sslmode=prefer`, and `prefer` is libpq's own default. |
| 13 | */ |
| 14 | const OPPORTUNISTIC_SSL_MODES: ReadonlySet<string> = new Set([ |
| 15 | "prefer", |
| 16 | "allow", |
| 17 | ]); |
| 18 | |
| 19 | /** |
| 20 | * Resolve the `ssl` option to hand `@effect/sql-pg` for a connection URL. |
| 21 | * |
| 22 | * When the caller left `ssl` implicit and the URL's `sslmode` is `prefer` or |
| 23 | * `allow`, returns `ssl: true` so the connection is TLS-on exactly as it was |
| 24 | * under node-postgres. Railway's `no-verify` mode defaults to TLS without |
| 25 | * certificate verification. Explicit caller settings take precedence, and |
| 26 | * other URL modes keep driving the decision inside `@effect/sql-pg`. |
| 27 | * |
| 28 | * (`@effect/sql-pg` < rc.115 also sent no TLS SNI; that was fixed upstream in |
| 29 | * Effect-TS/effect#8174, so this helper no longer sets `servername`.) |
| 30 | */ |
| 31 | export const resolveSsl = ( |
| 32 | url: Redacted.Redacted<string>, |
| 33 | ssl: PgSsl, |
| 34 | ): PgSsl => { |
| 35 | if (ssl !== undefined) return ssl; |
| 36 | let parsed: URL; |
| 37 | try { |
| 38 | parsed = new URL(Redacted.value(url)); |
| 39 | } catch { |
| 40 | // Let `@effect/sql-pg` report the malformed URL. |
| 41 | return ssl; |
| 42 | } |
| 43 | const sslmode = parsed.searchParams.get("sslmode"); |
| 44 | if (sslmode === "no-verify") return { rejectUnauthorized: false }; |
| 45 | return sslmode !== null && OPPORTUNISTIC_SSL_MODES.has(sslmode) ? true : ssl; |
| 46 | }; |
| 47 | |
| 48 | /** |
| 49 | * Normalize the URL and TLS settings together for Effect's Postgres driver. |
| 50 | * Translate node-postgres's `sslmode=no-verify` to `require` with explicit |
| 51 | * TLS options so it also works with stricter URL parsers. Retain its verification |
| 52 | * choice in the TLS options. Other URL parameters and explicit TLS settings |
| 53 | * remain intact; credentials stay redacted. |
| 54 | */ |
| 55 | export const resolveConnectionOptions = ( |
| 56 | url: Redacted.Redacted<string>, |
| 57 | ssl?: PgSsl, |
| 58 | ): { url: Redacted.Redacted<string>; ssl: PgSsl } => { |
| 59 | const resolvedSsl = resolveSsl(url, ssl); |
| 60 | try { |
| 61 | const parsed = new URL(Redacted.value(url)); |
| 62 | if (parsed.searchParams.get("sslmode") === "no-verify") { |
| 63 | parsed.searchParams.set("sslmode", "require"); |
| 64 | return { url: Redacted.make(parsed.toString()), ssl: resolvedSsl }; |
| 65 | } |
| 66 | } catch { |
| 67 | // The driver owns malformed URL diagnostics. |
| 68 | } |
| 69 | return { url, ssl: resolvedSsl }; |
| 70 | }; |