feat: implement tenant isolation with PostgreSQL RLS

- users + tenant_memberships tables (tenant-scoped)
- RLS policy on tenant_memberships using set_config('app.current_tenant_id', ...)
- withTenantContext() helper for transaction-scoped tenant context
- separate non-superuser app role (b2bcall_app): the default Docker postgres
  user is SUPERUSER and always bypasses RLS even with FORCE, so the app must
  never connect through the migration/owner role. Documented in
  docs/TENANT_ISOLATION.md.
- automated isolation test proving tenant A never sees tenant B's data
This commit is contained in:
2026-08-28 05:47:23 -03:00
parent c0f29328bd
commit d66170c795
11 changed files with 676 additions and 6 deletions

28
scripts/db-setup-app-role.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Sets/rotates the password of the restricted, non-superuser Postgres role
# used by the running application (b2bcall_app). The role itself is created
# by the "app_role_and_grants" Prisma migration; this script only sets the
# secret, which must never be embedded in a committed migration file.
#
# Reads POSTGRES_USER/PASSWORD (superuser, to run the ALTER ROLE) and
# POSTGRES_APP_USER/POSTGRES_APP_PASSWORD from .env.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
set -a
# shellcheck disable=SC1091
source "$ROOT_DIR/.env"
set +a
: "${POSTGRES_APP_USER:?POSTGRES_APP_USER not set in .env}"
: "${POSTGRES_APP_PASSWORD:?POSTGRES_APP_PASSWORD not set in .env}"
docker exec -i b2bcall-postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
-v ON_ERROR_STOP=1 \
-v app_user="$POSTGRES_APP_USER" \
-v app_password="$POSTGRES_APP_PASSWORD" \
<<'SQL'
ALTER ROLE :"app_user" WITH PASSWORD :'app_password';
SQL
echo "Senha do role '${POSTGRES_APP_USER}' aplicada."