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;
|
idle_timeout: number;
|
||||||
max_lifetime: number;
|
max_lifetime: number;
|
||||||
ssl?: { rejectUnauthorized: true };
|
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 {
|
export function buildPostgresOptions(connectionString: string, purpose: DbPurpose, env: EnvMap = process.env): PostgresClientOptions {
|
||||||
const deployed = isDeployedEnv(env);
|
const deployed = isDeployedEnv(env);
|
||||||
if (deployed) assertDeployedUrl(connectionString, purpose, env);
|
if (deployed) assertDeployedUrl(connectionString, purpose, env);
|
||||||
@@ -185,10 +191,7 @@ export function buildPostgresOptions(connectionString: string, purpose: DbPurpos
|
|||||||
idle_timeout: 20,
|
idle_timeout: 20,
|
||||||
max_lifetime: 60 * 30,
|
max_lifetime: 60 * 30,
|
||||||
};
|
};
|
||||||
if (deployed) {
|
if (deployed) options.ssl = { rejectUnauthorized: true };
|
||||||
options.ssl = { rejectUnauthorized: true };
|
|
||||||
options.connection = { family: 4 };
|
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,13 @@ function postgresErrorCode(error: unknown): string | null {
|
|||||||
return 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> {
|
async function readRole(db: Db): Promise<FactRow> {
|
||||||
try {
|
try {
|
||||||
const rows = await db.withService((query) =>
|
const rows = await db.withService((query) =>
|
||||||
@@ -132,7 +139,7 @@ async function readRole(db: Db): Promise<FactRow> {
|
|||||||
return facts;
|
return facts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DatabaseGuardError) throw error;
|
if (error instanceof DatabaseGuardError) throw error;
|
||||||
throw new DatabaseGuardError("identidade_indisponivel");
|
throw new DatabaseGuardError(unavailableIdentityCode(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DatabaseGuardError,
|
DatabaseGuardError,
|
||||||
assertDeployedUrl,
|
assertDeployedUrl,
|
||||||
assertLocalDatabaseUrl,
|
assertLocalDatabaseUrl,
|
||||||
|
buildPostgresOptions,
|
||||||
expectedPoolerHost,
|
expectedPoolerHost,
|
||||||
resolvePoolMax,
|
resolvePoolMax,
|
||||||
scrubDbError,
|
scrubDbError,
|
||||||
@@ -12,7 +13,8 @@ import {
|
|||||||
import { readFileSync, readdirSync } from "node:fs";
|
import { readFileSync, readdirSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { assertInitAuthorized, projectIdentityDefinitionSql, projectIdentityRevertSql } from "@/infra/db/project-identity";
|
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 = "abcdefghijabcdefghij";
|
||||||
const REF_COM_DIGITO = "abcd1234abcd1234wxyz";
|
const REF_COM_DIGITO = "abcd1234abcd1234wxyz";
|
||||||
@@ -165,6 +167,16 @@ describe("barreira do banco de homologação", () => {
|
|||||||
expect(resolvePoolMax("web", env)).toBe(2);
|
expect(resolvePoolMax("web", env)).toBe(2);
|
||||||
expect(resolvePoolMax("worker", env)).toBe(2);
|
expect(resolvePoolMax("worker", env)).toBe(2);
|
||||||
expect(resolvePoolMax("admin", env)).toBe(1);
|
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", () => {
|
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", () => {
|
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"));
|
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");
|
expect(scrubbed).not.toContain("segredo");
|
||||||
|
|||||||
Reference in new issue
Block a user