fix(db): remove invalid postgres startup parameter
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b410fa4559
commit
60f15a3576
3 files changed
+65
-8
No files matched your search
@@ -170,10 +170,16 @@ export type PostgresClientOptions = {
|
||||
idle_timeout: number;
|
||||
max_lifetime: number;
|
||||
ssl?: { rejectUnauthorized: true };
|
||||
connection?: { family: 4 };
|
||||
};
|
||||
|
||||
/** Opções do driver. Em ambiente implantado o certificado é verificado sempre. */
|
||||
/**
|
||||
* Opções do driver. Em ambiente implantado o certificado é verificado sempre.
|
||||
*
|
||||
* `connection` no postgres.js é um parâmetro de startup, não uma opção do
|
||||
* socket. Enviar `family=4` faz o PostgreSQL recusar a sessão com 42704
|
||||
* (`unrecognized configuration parameter "family"`) antes da primeira
|
||||
* consulta. O IPv4 continua garantido pelo host do Session Pooler.
|
||||
*/
|
||||
export function buildPostgresOptions(connectionString: string, purpose: DbPurpose, env: EnvMap = process.env): PostgresClientOptions {
|
||||
const deployed = isDeployedEnv(env);
|
||||
if (deployed) assertDeployedUrl(connectionString, purpose, env);
|
||||
@@ -185,10 +191,7 @@ export function buildPostgresOptions(connectionString: string, purpose: DbPurpos
|
||||
idle_timeout: 20,
|
||||
max_lifetime: 60 * 30,
|
||||
};
|
||||
if (deployed) {
|
||||
options.ssl = { rejectUnauthorized: true };
|
||||
options.connection = { family: 4 };
|
||||
}
|
||||
if (deployed) options.ssl = { rejectUnauthorized: true };
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,13 @@ function postgresErrorCode(error: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Código do driver, sem a mensagem. A mensagem pode conter usuário, host ou senha. */
|
||||
function unavailableIdentityCode(error: unknown): string {
|
||||
const code = postgresErrorCode(error);
|
||||
if (code && /^[A-Z0-9_]{2,48}$/.test(code)) return `identidade_indisponivel:${code}`;
|
||||
return "identidade_indisponivel";
|
||||
}
|
||||
|
||||
async function readRole(db: Db): Promise<FactRow> {
|
||||
try {
|
||||
const rows = await db.withService((query) =>
|
||||
@@ -132,7 +139,7 @@ async function readRole(db: Db): Promise<FactRow> {
|
||||
return facts;
|
||||
} catch (error) {
|
||||
if (error instanceof DatabaseGuardError) throw error;
|
||||
throw new DatabaseGuardError("identidade_indisponivel");
|
||||
throw new DatabaseGuardError(unavailableIdentityCode(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
DatabaseGuardError,
|
||||
assertDeployedUrl,
|
||||
assertLocalDatabaseUrl,
|
||||
buildPostgresOptions,
|
||||
expectedPoolerHost,
|
||||
resolvePoolMax,
|
||||
scrubDbError,
|
||||
@@ -12,7 +13,8 @@ import {
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { assertInitAuthorized, projectIdentityDefinitionSql, projectIdentityRevertSql } from "@/infra/db/project-identity";
|
||||
import { evaluateAdminIdentity, evaluateProjectIdentity, evaluateRuntimeRole, type AdminAttestation } from "@/infra/db/target-guard";
|
||||
import { assertAdminTarget, evaluateAdminIdentity, evaluateProjectIdentity, evaluateRuntimeRole, type AdminAttestation } from "@/infra/db/target-guard";
|
||||
import type { Db } from "@/infra/db/types";
|
||||
|
||||
const REF = "abcdefghijabcdefghij";
|
||||
const REF_COM_DIGITO = "abcd1234abcd1234wxyz";
|
||||
@@ -165,6 +167,16 @@ describe("barreira do banco de homologação", () => {
|
||||
expect(resolvePoolMax("web", env)).toBe(2);
|
||||
expect(resolvePoolMax("worker", env)).toBe(2);
|
||||
expect(resolvePoolMax("admin", env)).toBe(1);
|
||||
const deployed = buildPostgresOptions(adminUrl(), "admin", env);
|
||||
const production = buildPostgresOptions(adminUrl(), "admin", { ...env, ADAPTER_ENV: "production" });
|
||||
for (const options of [deployed, production]) {
|
||||
expect(options.ssl).toEqual({ rejectUnauthorized: true });
|
||||
expect(options).not.toHaveProperty("connection");
|
||||
expect(JSON.stringify(options)).not.toContain("family");
|
||||
}
|
||||
expect(buildPostgresOptions("postgres://adaptador:local@127.0.0.1:5432/adaptador", "local", { ADAPTER_ENV: "development" })).not.toHaveProperty(
|
||||
"connection",
|
||||
);
|
||||
});
|
||||
|
||||
it("recusa a credencial administrativa na web e BYPASSRLS no worker", () => {
|
||||
@@ -207,6 +219,41 @@ describe("barreira do banco de homologação", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("classifica a falha de conexão pelo código do driver e omite a mensagem", async () => {
|
||||
const db = {
|
||||
exec: async () => undefined,
|
||||
close: async () => undefined,
|
||||
withUser: async () => {
|
||||
throw new Error("nao");
|
||||
},
|
||||
withService: async () => {
|
||||
throw Object.assign(new Error("postgres://postgres.ref:segredo@host/db family"), { code: "42704" });
|
||||
},
|
||||
} as Db;
|
||||
await expect(assertAdminTarget(db, env)).rejects.toMatchObject({
|
||||
code: "identidade_indisponivel:42704",
|
||||
});
|
||||
await expect(assertAdminTarget(db, env)).rejects.not.toThrow(/segredo/);
|
||||
|
||||
const semCodigo = {
|
||||
...db,
|
||||
withService: async () => {
|
||||
throw new Error("postgres://postgres.ref:segredo@host/db");
|
||||
},
|
||||
} as Db;
|
||||
await expect(assertAdminTarget(semCodigo, env)).rejects.toMatchObject({ code: "identidade_indisponivel" });
|
||||
await expect(assertAdminTarget(semCodigo, env)).rejects.not.toThrow(/segredo/);
|
||||
|
||||
const codigoInseguro = {
|
||||
...db,
|
||||
withService: async () => {
|
||||
throw Object.assign(new Error("falha"), { code: "postgres://segredo" });
|
||||
},
|
||||
} as Db;
|
||||
await expect(assertAdminTarget(codigoInseguro, env)).rejects.toMatchObject({ code: "identidade_indisponivel" });
|
||||
await expect(assertAdminTarget(codigoInseguro, env)).rejects.not.toThrow(/segredo/);
|
||||
});
|
||||
|
||||
it("não deixa senha nem URL no erro visível", () => {
|
||||
const scrubbed = scrubDbError(new Error("falha em postgres://postgres.abc:segredo@aws-0-us-west-2.pooler.supabase.com:5432/postgres"));
|
||||
expect(scrubbed).not.toContain("segredo");
|
||||
|
||||
Reference in new issue
Block a user