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:
@@ -32,5 +32,47 @@ model Tenant {
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
memberships TenantMembership[]
|
||||
|
||||
@@map("tenants")
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
DISABLED
|
||||
|
||||
@@map("user_status")
|
||||
}
|
||||
|
||||
// Identidade global do usuário. NUNCA carrega tenant_id diretamente — o tenant
|
||||
// é sempre resolvido via TenantMembership (agente.md secao 31).
|
||||
model User {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
email String @unique
|
||||
passwordHash String @map("password_hash")
|
||||
name String
|
||||
status UserStatus @default(ACTIVE)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
memberships TenantMembership[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// Tabela tenant-scoped protegida por Row Level Security (ver migration
|
||||
// 'tenant_isolation' e docs/TENANT_ISOLATION.md).
|
||||
model TenantMembership {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
tenantId String @map("tenant_id") @db.Uuid
|
||||
userId String @map("user_id") @db.Uuid
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@unique([tenantId, userId])
|
||||
@@index([tenantId])
|
||||
@@map("tenant_memberships")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user