diff --git a/src/js/internal/sql/shared.ts b/src/js/internal/sql/shared.ts index fc484b00c7..b58eb5d2d8 100644 --- a/src/js/internal/sql/shared.ts +++ b/src/js/internal/sql/shared.ts @@ -838,6 +838,10 @@ function parseOptions( } } + if (tls && sslMode === SSLMode.disable) { + sslMode = SSLMode.prefer; + } + if (sslMode !== SSLMode.disable && !tls?.serverName) { if (hostname) { tls = { ...tls, serverName: hostname }; @@ -846,10 +850,6 @@ function parseOptions( } } - if (tls && sslMode === SSLMode.disable) { - sslMode = SSLMode.prefer; - } - port = Number(port); if (!Number.isSafeInteger(port) || port < 1 || port > 65535) { diff --git a/test/regression/issue/26369.test.ts b/test/regression/issue/26369.test.ts new file mode 100644 index 0000000000..4263bd704e --- /dev/null +++ b/test/regression/issue/26369.test.ts @@ -0,0 +1,106 @@ +import { SQL } from "bun"; +import { describe, expect, test } from "bun:test"; + +describe("Issue #26369 - SNI hostname not sent when using tls: true with individual connection options", () => { + test("tls: true with host option should auto-populate serverName", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: true, + adapter: "postgres", + }); + + // Verify serverName is automatically set from the hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls: true with hostname option should auto-populate serverName", () => { + const sql = new SQL({ + hostname: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: true, + adapter: "postgres", + }); + + // Verify serverName is automatically set from the hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls with explicit serverName should preserve it", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: { serverName: "custom-server-name.example.com" }, + adapter: "postgres", + }); + + // Verify explicit serverName is preserved + expect(sql.options.tls).toEqual({ serverName: "custom-server-name.example.com" }); + + sql.close(); + }); + + test("URL with sslmode=require should auto-populate serverName", () => { + const sql = new SQL("postgresql://user:pass@example-host.neon.tech/db?sslmode=require"); + + // Verify serverName is automatically set from the URL hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("URL without sslmode but with tls: true option should auto-populate serverName", () => { + const sql = new SQL("postgresql://user:pass@example-host.neon.tech/db", { + tls: true, + }); + + // Verify serverName is automatically set from the URL hostname + expect(sql.options.tls).toEqual({ serverName: "example-host.neon.tech" }); + + sql.close(); + }); + + test("tls object with other properties should also get serverName", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + tls: { rejectUnauthorized: false }, + adapter: "postgres", + }); + + // Verify serverName is added alongside existing tls properties + expect(sql.options.tls).toEqual({ + rejectUnauthorized: false, + serverName: "example-host.neon.tech", + }); + + sql.close(); + }); + + test("no tls option should not set tls in options", () => { + const sql = new SQL({ + host: "example-host.neon.tech", + username: "user", + password: "pass", + database: "db", + adapter: "postgres", + }); + + // Verify tls is not set when not explicitly enabled + expect(sql.options.tls).toBeUndefined(); + + sql.close(); + }); +});