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: string): 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("grava a primeira identidade sem papéis web ou worker", async () => { await db.exec(` drop role if exists anon; drop role if exists authenticated; drop role if exists service_role; create role anon nologin nosuperuser nobypassrls; create role authenticated nologin nosuperuser nobypassrls; create role service_role nologin nosuperuser bypassrls; `); try { expect(await installProjectIdentity(db, REF)).toBe("criada"); expect(await installProjectIdentity(db, REF)).toBe("inalterada"); await expect(installProjectIdentity(db, OUTRO_REF)).rejects.toMatchObject({ code: "marcador_diverge" }); const estado = await db.withService((query) => query.query<{ operacional: number; migrations: string | null; flags: string | null; anon_exec: boolean; service_exec: boolean; project_ref: string; }>( `select (select count(*)::int from pg_catalog.pg_roles where rolname in ('adaptador_web', 'adaptador_worker')) as operacional, to_regclass('public.schema_migrations')::text as migrations, to_regclass('public.workspace_module_flags')::text as flags, has_function_privilege('anon', 'adaptador_guard.adapter_project_identity()', 'execute') as anon_exec, has_function_privilege('service_role', 'adaptador_guard.adapter_project_identity()', 'execute') as service_exec, i.project_ref from adaptador_guard.project_identity i`, ), ); expect(estado[0]).toEqual({ operacional: 0, migrations: null, flags: null, anon_exec: false, service_exec: false, project_ref: REF, }); } finally { await revertProjectIdentity(db).catch(async () => { await db.exec(`drop schema if exists adaptador_guard cascade`).catch(() => undefined); }); await db.exec(` drop role if exists anon; drop role if exists authenticated; drop role if exists service_role; `).catch(() => undefined); } }); 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); }); });