Phase 2: Enforce Row-Level Security at the PostgreSQL level
- Rewrite rls.ts with withTenant()/withBypassRLS() using parameterized set_config() in interactive Prisma transactions (no SQL injection) - Add FORCE ROW LEVEL SECURITY on all 15 tenant-scoped tables - Add __bypass__ sentinel and WITH CHECK clauses to every RLS policy - Explicitly DISABLE RLS on tenants, tenant_hostnames, sessions - Wrap all API route and page loader queries in withTenant() - Wrap validateSession() in withBypassRLS() (joins RLS-protected users) - Keep existing tenantId where-clause filters as optimization layer - Add postgresql-client to Dockerfile and auto-apply RLS via entrypoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+25
-20
@@ -1,5 +1,6 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
import { prisma } from './db.js';
|
||||
import { withBypassRLS } from './rls.js';
|
||||
|
||||
const SALT_ROUNDS = 12;
|
||||
const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
@@ -21,30 +22,34 @@ export async function createSession(userId: string): Promise<{ id: string; expir
|
||||
}
|
||||
|
||||
export async function validateSession(sessionId: string) {
|
||||
const session = await prisma.session.findUnique({
|
||||
where: { id: sessionId },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
globalRole: true,
|
||||
tenantRole: true,
|
||||
tenantId: true
|
||||
// Uses withBypassRLS because this joins to the users table (RLS-protected)
|
||||
// and runs before tenant context is established in hooks.server.ts.
|
||||
return withBypassRLS(async (tx) => {
|
||||
const session = await tx.session.findUnique({
|
||||
where: { id: sessionId },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
globalRole: true,
|
||||
tenantRole: true,
|
||||
tenantId: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!session) return null;
|
||||
|
||||
if (session.expiresAt < new Date()) {
|
||||
await tx.session.delete({ where: { id: sessionId } });
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
});
|
||||
|
||||
if (!session) return null;
|
||||
|
||||
if (session.expiresAt < new Date()) {
|
||||
await prisma.session.delete({ where: { id: sessionId } });
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function deleteSession(sessionId: string) {
|
||||
|
||||
Reference in New Issue
Block a user