fix(db): implementa identidade persistente segura para homologacao
This commit is contained in:
1 parent
2c8c5d9507
commit
a4f0a2f649
11 files changed
+1146
-106
No files matched your search
@@ -0,0 +1,361 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { createPostgresDb } from "@/infra/db/postgres";
|
||||
import { installProjectIdentity, projectIdentityDefinitionSql, revertProjectIdentity } from "@/infra/db/project-identity";
|
||||
import { migrate } from "@/infra/db/migrate";
|
||||
import { assertAdminTarget, assertDatabaseGuard, readProjectIdentity } from "@/infra/db/target-guard";
|
||||
import type { Db } from "@/infra/db/types";
|
||||
|
||||
/**
|
||||
* Identidade persistente contra PostgreSQL 17 descartável.
|
||||
* A suíte só roda com ADAPTER_TEST_DATABASE_URL. Sem essa URL o caso fica
|
||||
* pendente e não conta como aprovação.
|
||||
*/
|
||||
const url = process.env.ADAPTER_TEST_DATABASE_URL;
|
||||
const enabled = Boolean(url);
|
||||
const REF = "abcd1234abcd1234wxyz";
|
||||
const OUTRO_REF = "zzzzzzzzzzzzzzzzzzz1";
|
||||
const env = {
|
||||
ADAPTER_ENV: "staging",
|
||||
ADAPTER_DB_PROJECT_REF: REF,
|
||||
ADAPTER_DB_POOLER_HOST: "aws-1-us-west-2.pooler.supabase.com",
|
||||
};
|
||||
|
||||
let db: Db;
|
||||
|
||||
function roleUrl(role: "adaptador_web" | "adaptador_worker"): string {
|
||||
const parsed = new URL(url as string);
|
||||
parsed.username = role;
|
||||
parsed.password = "";
|
||||
return parsed.toString();
|
||||
}
|
||||
|
||||
describe.skipIf(!enabled)("identidade no PostgreSQL 17", () => {
|
||||
beforeAll(async () => {
|
||||
db = createPostgresDb(url as string);
|
||||
await db.exec(`
|
||||
drop schema if exists adaptador_guard cascade;
|
||||
drop schema if exists public cascade;
|
||||
create schema public;
|
||||
drop schema if exists auth cascade;
|
||||
create schema auth;
|
||||
drop schema if exists storage cascade;
|
||||
create schema storage;
|
||||
`);
|
||||
await db.exec(`
|
||||
do $$
|
||||
begin
|
||||
if exists (select 1 from pg_catalog.pg_roles where rolname = 'adaptador_web') then
|
||||
drop role adaptador_web;
|
||||
end if;
|
||||
if exists (select 1 from pg_catalog.pg_roles where rolname = 'adaptador_worker') then
|
||||
drop role adaptador_worker;
|
||||
end if;
|
||||
if exists (select 1 from pg_catalog.pg_roles where rolname = 'app_user') then
|
||||
drop role app_user;
|
||||
end if;
|
||||
if exists (select 1 from pg_catalog.pg_roles where rolname = 'ident_owner') then
|
||||
drop role ident_owner;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
`);
|
||||
await db.exec(`alter database postgres set "app.settings.jwt_exp" = '3600'`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!db) return;
|
||||
await db.exec(`drop schema if exists adaptador_guard cascade`).catch(() => undefined);
|
||||
await db.close();
|
||||
});
|
||||
|
||||
it("não cria a identidade na migration e impede a web de alterá-la", async () => {
|
||||
await db.exec(`create schema adaptador_guard`);
|
||||
await expect(installProjectIdentity(db, REF)).rejects.toMatchObject({ code: "marcador_indisponivel" });
|
||||
const vazio = await db.withService((query) =>
|
||||
query.query<{ relname: string | null }>(
|
||||
`select c.relname
|
||||
from pg_catalog.pg_class c
|
||||
join pg_catalog.pg_namespace n on n.oid = c.relnamespace
|
||||
where n.nspname = 'adaptador_guard' and c.relkind = 'r'`,
|
||||
),
|
||||
);
|
||||
expect(vazio).toEqual([]);
|
||||
await db.exec(`drop schema adaptador_guard restrict`);
|
||||
|
||||
const original = projectIdentityDefinitionSql(REF);
|
||||
const quebrado = original.replace(
|
||||
/end\s*\$identity\$;\s*$/,
|
||||
"raise exception 'falha_injetada';\nend\n$identity$;\n",
|
||||
);
|
||||
expect(quebrado).toContain("falha_injetada");
|
||||
await expect(db.exec(quebrado)).rejects.toThrow(/falha_injetada/);
|
||||
const aposFalha = await db.withService((query) =>
|
||||
query.query<{ exists: boolean }>(
|
||||
`select exists(select 1 from pg_catalog.pg_namespace where nspname = 'adaptador_guard') as exists`,
|
||||
),
|
||||
);
|
||||
expect(aposFalha[0]?.exists).toBe(false);
|
||||
|
||||
await migrate(db, { applyIdentityShim: true });
|
||||
const antes = await db.withService((query) =>
|
||||
query.query<{ exists: boolean }>(
|
||||
`select exists(select 1 from pg_catalog.pg_namespace where nspname = 'adaptador_guard') as exists`,
|
||||
),
|
||||
);
|
||||
expect(antes[0]?.exists).toBe(false);
|
||||
await db.exec(`
|
||||
drop owned by adaptador_web, adaptador_worker, app_user;
|
||||
drop role adaptador_web;
|
||||
drop role adaptador_worker;
|
||||
drop role app_user;
|
||||
drop schema public cascade;
|
||||
create schema public;
|
||||
`);
|
||||
|
||||
expect(await installProjectIdentity(db, REF)).toBe("criada");
|
||||
await migrate(db, { applyIdentityShim: true });
|
||||
const loginAntes = await db.withService((query) =>
|
||||
query.query<{ rolname: string; rolcanlogin: boolean; rolsuper: boolean; rolbypassrls: boolean }>(
|
||||
`select rolname, rolcanlogin, rolsuper, rolbypassrls
|
||||
from pg_catalog.pg_roles
|
||||
where rolname in ('adaptador_web', 'adaptador_worker', 'app_user')`,
|
||||
),
|
||||
);
|
||||
expect(loginAntes).toHaveLength(3);
|
||||
for (const role of loginAntes) {
|
||||
expect(role.rolcanlogin, role.rolname).toBe(false);
|
||||
expect(role.rolsuper, role.rolname).toBe(false);
|
||||
expect(role.rolbypassrls, role.rolname).toBe(false);
|
||||
}
|
||||
|
||||
const semGrant = await db.withService((query) =>
|
||||
query.query<{ web: boolean; worker: boolean; app: boolean }>(
|
||||
`select
|
||||
has_function_privilege('adaptador_web', 'adaptador_guard.adapter_project_identity()', 'execute') as web,
|
||||
has_function_privilege('adaptador_worker', 'adaptador_guard.adapter_project_identity()', 'execute') as worker,
|
||||
has_function_privilege('app_user', 'adaptador_guard.adapter_project_identity()', 'execute') as app`,
|
||||
),
|
||||
);
|
||||
expect(semGrant[0]).toEqual({ web: false, worker: false, app: false });
|
||||
|
||||
await db.exec(`alter role adaptador_web login; alter role adaptador_worker login`);
|
||||
const webAntes = createPostgresDb(roleUrl("adaptador_web"));
|
||||
await expect(assertDatabaseGuard(webAntes, "web", roleUrl("adaptador_web"), env)).rejects.toMatchObject({
|
||||
code: "marcador_indisponivel",
|
||||
});
|
||||
await webAntes.close();
|
||||
|
||||
expect(await installProjectIdentity(db, REF)).toBe("inalterada");
|
||||
const antesDaSegunda = await db.withService((query) =>
|
||||
query.query<{ project_ref: string; xmin: string }>(
|
||||
`select project_ref, xmin::text as xmin from adaptador_guard.project_identity`,
|
||||
),
|
||||
);
|
||||
expect(await installProjectIdentity(db, REF)).toBe("inalterada");
|
||||
const depoisDaSegunda = await db.withService((query) =>
|
||||
query.query<{ project_ref: string; xmin: string }>(
|
||||
`select project_ref, xmin::text as xmin from adaptador_guard.project_identity`,
|
||||
),
|
||||
);
|
||||
expect(depoisDaSegunda).toEqual(antesDaSegunda);
|
||||
await expect(installProjectIdentity(db, OUTRO_REF)).rejects.toMatchObject({ code: "marcador_diverge" });
|
||||
const aposRefErrado = await db.withService((query) =>
|
||||
query.query<{ project_ref: string }>(`select project_ref from adaptador_guard.project_identity`),
|
||||
);
|
||||
expect(aposRefErrado[0]?.project_ref).toBe(REF);
|
||||
|
||||
await assertAdminTarget(db, env);
|
||||
await assertDatabaseGuard(db, "admin", url, env);
|
||||
await expect(assertDatabaseGuard(db, "admin", url, { ...env, ADAPTER_DB_PROJECT_REF: OUTRO_REF })).rejects.toMatchObject({
|
||||
code: "marcador_diverge",
|
||||
});
|
||||
|
||||
const web = createPostgresDb(roleUrl("adaptador_web"));
|
||||
const worker = createPostgresDb(roleUrl("adaptador_worker"));
|
||||
try {
|
||||
await assertDatabaseGuard(web, "web", roleUrl("adaptador_web"), env);
|
||||
await assertDatabaseGuard(worker, "worker", roleUrl("adaptador_worker"), env);
|
||||
await expect(
|
||||
web.exec(`update adaptador_guard.project_identity set project_ref = '${OUTRO_REF}'`),
|
||||
).rejects.toMatchObject({ code: "42501" });
|
||||
await expect(worker.exec(`delete from adaptador_guard.project_identity`)).rejects.toMatchObject({ code: "42501" });
|
||||
} finally {
|
||||
await web.close();
|
||||
await worker.close();
|
||||
}
|
||||
|
||||
await expect(db.exec(`update adaptador_guard.project_identity set project_name = project_name`)).rejects.toMatchObject({
|
||||
code: "42501",
|
||||
});
|
||||
await expect(db.exec(`delete from adaptador_guard.project_identity`)).rejects.toMatchObject({ code: "42501" });
|
||||
await expect(
|
||||
db.exec(`do $$ begin set local role app_user; perform * from adaptador_guard.adapter_project_identity(); end $$;`),
|
||||
).rejects.toMatchObject({ code: "42501" });
|
||||
|
||||
await db.exec(`
|
||||
create role ident_owner nologin nosuperuser bypassrls;
|
||||
grant usage on schema adaptador_guard to ident_owner;
|
||||
alter table adaptador_guard.project_identity owner to ident_owner;
|
||||
alter function adaptador_guard.adapter_project_identity() owner to ident_owner;
|
||||
`);
|
||||
const comoDono = await db.withService((query) =>
|
||||
query.query<{ project_name: string; project_ref: string; rolsuper: boolean; rolbypassrls: boolean }>(
|
||||
`select i.project_name, i.project_ref, r.rolsuper, r.rolbypassrls
|
||||
from pg_catalog.pg_roles r
|
||||
cross join adaptador_guard.adapter_project_identity() i
|
||||
where r.rolname = 'ident_owner'`,
|
||||
),
|
||||
);
|
||||
expect(comoDono[0]).toMatchObject({
|
||||
project_name: "adaptador-planilha-hml",
|
||||
project_ref: REF,
|
||||
rolsuper: false,
|
||||
rolbypassrls: true,
|
||||
});
|
||||
|
||||
const lida = await readProjectIdentity(db);
|
||||
expect(lida).toEqual({ projectName: "adaptador-planilha-hml", projectRef: REF });
|
||||
|
||||
const marcadores = await db.withService((query) =>
|
||||
query.query<{ item: string }>(
|
||||
`select item
|
||||
from pg_catalog.pg_db_role_setting s
|
||||
cross join lateral unnest(s.setconfig) as item
|
||||
where s.setdatabase = (select oid from pg_catalog.pg_database where datname = current_database())
|
||||
and s.setrole = 0`,
|
||||
),
|
||||
);
|
||||
const itens = marcadores.map((row) => row.item);
|
||||
expect(itens).toContain("app.settings.jwt_exp=3600");
|
||||
expect(itens.some((item) => item.startsWith("app.hml_project"))).toBe(false);
|
||||
|
||||
const internos = await db.withService((query) =>
|
||||
query.query<{ schema_name: string; total: number }>(
|
||||
`select n.nspname as schema_name, count(c.oid)::int as total
|
||||
from pg_catalog.pg_namespace n
|
||||
left join pg_catalog.pg_class c on c.relnamespace = n.oid and c.relkind in ('r', 'p', 'v', 'm')
|
||||
where n.nspname in ('auth', 'storage')
|
||||
group by n.nspname
|
||||
order by n.nspname`,
|
||||
),
|
||||
);
|
||||
expect(internos).toEqual([
|
||||
{ schema_name: "auth", total: 0 },
|
||||
{ schema_name: "storage", total: 0 },
|
||||
]);
|
||||
|
||||
const proteção = await db.withService((query) =>
|
||||
query.query<{ relrowsecurity: boolean; relforcerowsecurity: boolean }>(
|
||||
`select c.relrowsecurity, c.relforcerowsecurity
|
||||
from pg_catalog.pg_class c
|
||||
join pg_catalog.pg_namespace n on n.oid = c.relnamespace
|
||||
where n.nspname = 'adaptador_guard' and c.relname = 'project_identity'`,
|
||||
),
|
||||
);
|
||||
expect(proteção[0]).toEqual({ relrowsecurity: true, relforcerowsecurity: true });
|
||||
|
||||
const definidor = await db.withService((query) =>
|
||||
query.query<{ proname: string; prosecdef: boolean; proconfig: string[] | null }>(
|
||||
`select p.proname, p.prosecdef, p.proconfig
|
||||
from pg_catalog.pg_proc p
|
||||
join pg_catalog.pg_namespace n on n.oid = p.pronamespace
|
||||
where n.nspname = 'adaptador_guard'
|
||||
order by p.proname`,
|
||||
),
|
||||
);
|
||||
expect(definidor).toEqual([
|
||||
{ proname: "adapter_project_identity", prosecdef: true, proconfig: ["search_path=pg_catalog"] },
|
||||
{ proname: "reject_identity_change", prosecdef: false, proconfig: ["search_path=pg_catalog"] },
|
||||
]);
|
||||
|
||||
await db.exec(`
|
||||
create table adaptador_guard.extra (id int);
|
||||
`);
|
||||
await expect(installProjectIdentity(db, REF)).rejects.toMatchObject({ code: "marcador_indisponivel" });
|
||||
await db.exec(`drop table adaptador_guard.extra`);
|
||||
|
||||
await db.exec(`
|
||||
do $$
|
||||
begin
|
||||
if not exists (select 1 from pg_catalog.pg_roles where rolname = 'anon') then
|
||||
create role anon nologin nosuperuser nobypassrls;
|
||||
end if;
|
||||
if not exists (select 1 from pg_catalog.pg_roles where rolname = 'authenticated') then
|
||||
create role authenticated nologin nosuperuser nobypassrls;
|
||||
end if;
|
||||
if not exists (select 1 from pg_catalog.pg_roles where rolname = 'service_role') then
|
||||
create role service_role nologin nosuperuser bypassrls;
|
||||
end if;
|
||||
end
|
||||
$$;
|
||||
`);
|
||||
expect(await installProjectIdentity(db, REF)).toBe("inalterada");
|
||||
const privilegios = await db.withService((query) =>
|
||||
query.query<{ papel: string; uso: boolean; leitura: boolean; alteracao: boolean; executar: boolean; criar: boolean }>(
|
||||
`select papel, uso, leitura, alteracao, executar, criar
|
||||
from (
|
||||
select 'public'::text as papel,
|
||||
has_schema_privilege('public', 'adaptador_guard', 'usage') as uso,
|
||||
has_schema_privilege('public', 'adaptador_guard', 'create') as criar,
|
||||
has_table_privilege('public', 'adaptador_guard.project_identity', 'select') as leitura,
|
||||
has_table_privilege('public', 'adaptador_guard.project_identity', 'update') as alteracao,
|
||||
has_function_privilege('public', 'adaptador_guard.adapter_project_identity()', 'execute') as executar
|
||||
union all
|
||||
select r.rolname,
|
||||
has_schema_privilege(r.oid, 'adaptador_guard', 'usage'),
|
||||
has_schema_privilege(r.oid, 'adaptador_guard', 'create'),
|
||||
has_table_privilege(r.oid, 'adaptador_guard.project_identity', 'select'),
|
||||
has_table_privilege(r.oid, 'adaptador_guard.project_identity', 'update'),
|
||||
has_function_privilege(r.oid, 'adaptador_guard.adapter_project_identity()', 'execute')
|
||||
from pg_catalog.pg_roles r
|
||||
where r.rolname in ('anon', 'authenticated', 'service_role', 'app_user', 'adaptador_web', 'adaptador_worker')
|
||||
) privilegio
|
||||
order by papel`,
|
||||
),
|
||||
);
|
||||
const porPapel = new Map(privilegios.map((row) => [row.papel, row]));
|
||||
for (const papel of ["public", "anon", "authenticated", "service_role", "app_user"]) {
|
||||
expect(porPapel.get(papel), papel).toMatchObject({ uso: false, leitura: false, alteracao: false, executar: false, criar: false });
|
||||
}
|
||||
for (const papel of ["adaptador_web", "adaptador_worker"]) {
|
||||
expect(porPapel.get(papel), papel).toMatchObject({ uso: true, leitura: false, alteracao: false, executar: true, criar: false });
|
||||
}
|
||||
await expect(
|
||||
db.exec(`do $$ begin set local role service_role; perform * from adaptador_guard.adapter_project_identity(); end $$;`),
|
||||
).rejects.toMatchObject({ code: "42501" });
|
||||
await expect(
|
||||
db.exec(`do $$ begin set local role adaptador_web; create table adaptador_guard.extra (id int); end $$;`),
|
||||
).rejects.toMatchObject({ code: "42501" });
|
||||
|
||||
await db.exec(`create view public.identity_leak as select project_name from adaptador_guard.project_identity`);
|
||||
await expect(revertProjectIdentity(db)).rejects.toMatchObject({ code: "reversao_recusada" });
|
||||
const vazou = await db.withService((query) =>
|
||||
query.query<{ nome: string | null }>(`select to_regclass('public.identity_leak')::text as nome`),
|
||||
);
|
||||
expect(vazou[0]?.nome).toBe("identity_leak");
|
||||
const aindaLa = await db.withService((query) =>
|
||||
query.query<{ exists: boolean }>(
|
||||
`select exists(select 1 from pg_catalog.pg_namespace where nspname = 'adaptador_guard') as exists`,
|
||||
),
|
||||
);
|
||||
expect(aindaLa[0]?.exists).toBe(true);
|
||||
await db.exec(`drop view public.identity_leak`);
|
||||
await revertProjectIdentity(db);
|
||||
const restante = await db.withService((query) =>
|
||||
query.query<{ nspname: string; profiles: string | null }>(
|
||||
`select n.nspname, to_regclass('public.profiles')::text as profiles
|
||||
from pg_catalog.pg_namespace n
|
||||
where n.nspname in ('public', 'auth', 'storage', 'adaptador_guard')
|
||||
order by n.nspname`,
|
||||
),
|
||||
);
|
||||
expect(restante.map((row) => row.nspname)).toEqual(["auth", "public", "storage"]);
|
||||
expect(restante.every((row) => row.profiles === "profiles")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(enabled)("identidade no PostgreSQL 17", () => {
|
||||
it("PENDENTE: defina ADAPTER_TEST_DATABASE_URL para executar", () => {
|
||||
expect(enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -9,9 +9,13 @@ import {
|
||||
resolvePoolMax,
|
||||
scrubDbError,
|
||||
} from "@/infra/db/connection-policy";
|
||||
import { evaluateAdminIdentity, evaluateRuntimeRole, type AdminAttestation } from "@/infra/db/target-guard";
|
||||
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";
|
||||
|
||||
const REF = "abcdefghijabcdefghij";
|
||||
const REF_COM_DIGITO = "abcd1234abcd1234wxyz";
|
||||
const DASHBOARD_HOST = "aws-1-us-west-2.pooler.supabase.com";
|
||||
|
||||
function adminUrl(user = `postgres.${REF}`, sslmode = "verify-full", host = DASHBOARD_HOST): string {
|
||||
@@ -26,10 +30,8 @@ function attestation(overrides: Partial<AdminAttestation> = {}): AdminAttestatio
|
||||
rolbypassrls: true,
|
||||
hasAuth: true,
|
||||
hasStorage: true,
|
||||
databaseSettings: new Map([
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", REF],
|
||||
]),
|
||||
projectName: AUTHORIZED_HML_PROJECT,
|
||||
projectRef: REF,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -44,24 +46,9 @@ describe("barreira do banco de homologação", () => {
|
||||
});
|
||||
|
||||
it("reconhece o postgres do Supabase gerenciado sem tratar BYPASSRLS como autorização", () => {
|
||||
const settings = new Map([
|
||||
["app.settings.jwt_exp", "3600"],
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", REF],
|
||||
]);
|
||||
expect(() => evaluateAdminIdentity(attestation({ rolsuper: false, rolbypassrls: true }), env)).not.toThrow();
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(attestation({ rolsuper: false, rolbypassrls: true, databaseSettings: settings }), env),
|
||||
).not.toThrow();
|
||||
expect(settings.get("app.settings.jwt_exp")).toBe("3600");
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(
|
||||
attestation({
|
||||
rolsuper: false,
|
||||
rolbypassrls: true,
|
||||
databaseSettings: new Map([["app.settings.jwt_exp", "3600"]]),
|
||||
}),
|
||||
env,
|
||||
),
|
||||
evaluateAdminIdentity(attestation({ projectName: null, projectRef: null, rolsuper: false, rolbypassrls: true }), env),
|
||||
).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(attestation({ sessionUser: "supabase_admin", rolsuper: false, rolbypassrls: true }), env),
|
||||
@@ -71,6 +58,48 @@ describe("barreira do banco de homologação", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("aceita Reference ID com dígito e recusa GUC ou variável como única prova", () => {
|
||||
const envComDigito = { ...env, ADAPTER_DB_PROJECT_REF: REF_COM_DIGITO };
|
||||
expect(() =>
|
||||
evaluateProjectIdentity({ projectName: AUTHORIZED_HML_PROJECT, projectRef: REF_COM_DIGITO }, envComDigito),
|
||||
).not.toThrow();
|
||||
expect(() => evaluateProjectIdentity({ projectName: null, projectRef: null }, envComDigito)).toThrow(/marcador_ausente/);
|
||||
expect(() => evaluateProjectIdentity({ projectName: AUTHORIZED_HML_PROJECT, projectRef: REF }, envComDigito)).toThrow(
|
||||
/marcador_diverge/,
|
||||
);
|
||||
expect(() => evaluateAdminIdentity(attestation(), { ...env, ADAPTER_DB_PROJECT_REF: "ABCD1234ABCD1234WXYZ" })).toThrow(
|
||||
/ref_ausente/,
|
||||
);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "staging" })).toThrow(/init_nao_autorizada/);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "development", ADAPTER_INIT_PROJECT_IDENTITY: "1" })).toThrow(
|
||||
/init_fora_de_homologacao/,
|
||||
);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "staging", ADAPTER_INIT_PROJECT_IDENTITY: "1" })).not.toThrow();
|
||||
const sql = projectIdentityDefinitionSql(REF_COM_DIGITO);
|
||||
expect(sql).toContain(REF_COM_DIGITO);
|
||||
expect(sql.split("do $identity$")).toHaveLength(2);
|
||||
expect(sql).toContain("security definer");
|
||||
expect(sql).toContain("set search_path = pg_catalog");
|
||||
expect(sql).toContain("revoke all on table adaptador_guard.project_identity from service_role");
|
||||
expect(sql.toLowerCase()).not.toContain("jwt_exp");
|
||||
expect(sql.toLowerCase()).not.toContain("alter database");
|
||||
expect(sql).not.toContain("auth.");
|
||||
expect(sql).not.toContain("storage.");
|
||||
const prepared = readFileSync(path.join(process.cwd(), "scripts/sql/init-project-identity.sql"), "utf8");
|
||||
expect(prepared).toContain("<<PROJECT_REF>>");
|
||||
expect(prepared).toContain("security definer");
|
||||
expect(prepared.toLowerCase()).not.toContain("cascade");
|
||||
const revert = projectIdentityRevertSql();
|
||||
const revertFile = readFileSync(path.join(process.cwd(), "scripts/sql/revert-project-identity.sql"), "utf8");
|
||||
expect(revert).toContain("drop schema adaptador_guard restrict;");
|
||||
expect(revert.toLowerCase()).not.toContain("cascade");
|
||||
expect(revertFile).toContain("drop schema adaptador_guard restrict;");
|
||||
expect(revertFile.toLowerCase()).not.toContain("cascade");
|
||||
const initScript = readFileSync(path.join(process.cwd(), "scripts/init-project-identity.ts"), "utf8");
|
||||
expect(initScript).not.toContain("console.log(databaseUrl");
|
||||
expect(initScript).not.toContain("console.log(process.env");
|
||||
});
|
||||
|
||||
it("exige o host do Dashboard e recusa índice, região ou domínio diferentes", () => {
|
||||
expect(POOLER_HOST_PATTERN.test(DASHBOARD_HOST)).toBe(true);
|
||||
expect(expectedPoolerHost(env)).toBe(DASHBOARD_HOST);
|
||||
@@ -90,18 +119,19 @@ describe("barreira do banco de homologação", () => {
|
||||
|
||||
it("recusa identidade ausente, marcador ausente e marcador divergente", () => {
|
||||
expect(() => evaluateAdminIdentity(attestation(), {})).toThrow(DatabaseGuardError);
|
||||
expect(() => evaluateAdminIdentity(attestation({ databaseSettings: new Map() }), env)).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(
|
||||
attestation({
|
||||
databaseSettings: new Map([
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", "zzzzzzzzzzzzzzzzzzzz"],
|
||||
]),
|
||||
}),
|
||||
env,
|
||||
),
|
||||
).toThrow(/marcador_diverge/);
|
||||
expect(() => evaluateAdminIdentity(attestation({ projectName: null, projectRef: null }), env)).toThrow(/marcador_ausente/);
|
||||
expect(() => evaluateAdminIdentity(attestation({ projectRef: "zzzzzzzzzzzzzzzzzzzz" }), env)).toThrow(/marcador_diverge/);
|
||||
expect(() => evaluateProjectIdentity({ projectName: "outro-projeto", projectRef: REF }, env)).toThrow(/marcador_diverge/);
|
||||
const migrations = readdirSync(path.join(process.cwd(), "migrations"), { recursive: true }).filter(
|
||||
(file) => typeof file === "string" && file.endsWith(".sql"),
|
||||
);
|
||||
for (const file of migrations) {
|
||||
const sql = readFileSync(path.join(process.cwd(), "migrations", file), "utf8");
|
||||
expect(sql, file).not.toContain("adaptador_guard");
|
||||
}
|
||||
for (const file of ["scripts/migrate.ts", "scripts/seed-staging.ts", "scripts/seed-dev.ts", "scripts/set-flag.ts"]) {
|
||||
expect(readFileSync(path.join(process.cwd(), file), "utf8"), file).not.toContain("installProjectIdentity");
|
||||
}
|
||||
});
|
||||
|
||||
it("não aceita só o nome postgres, o host ou um usuário sem o ref", () => {
|
||||
@@ -155,6 +185,7 @@ describe("barreira do banco de homologação", () => {
|
||||
expect(() =>
|
||||
evaluateRuntimeRole({ sessionUser: "adaptador_web", rolsuper: false, rolbypassrls: false }, "web", env),
|
||||
).not.toThrow();
|
||||
expect(() => evaluateProjectIdentity({ projectName: null, projectRef: null }, env)).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateRuntimeRole({ sessionUser: `adaptador_worker.${REF}`, rolsuper: false, rolbypassrls: false }, "worker", env),
|
||||
).not.toThrow();
|
||||
|
||||
Reference in new issue
Block a user