fix(auth): use set_config for transaction-scoped RLS context
All checks were successful
ci/woodpecker/push/api Pipeline was successful
All checks were successful
ci/woodpecker/push/api Pipeline was successful
This commit is contained in:
@@ -137,13 +137,13 @@ describe("RLS Context Integration", () => {
|
|||||||
queries: ["findMany"],
|
queries: ["findMany"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify SET LOCAL was called
|
// Verify transaction-local set_config calls were made
|
||||||
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
||||||
expect.arrayContaining(["SET LOCAL app.current_user_id = ", ""]),
|
expect.arrayContaining(["SELECT set_config('app.current_user_id', ", ", true)"]),
|
||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
||||||
expect.arrayContaining(["SET LOCAL app.current_workspace_id = ", ""]),
|
expect.arrayContaining(["SELECT set_config('app.current_workspace_id', ", ", true)"]),
|
||||||
workspaceId
|
workspaceId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ describe("RlsContextInterceptor", () => {
|
|||||||
|
|
||||||
expect(result).toEqual({ data: "test response" });
|
expect(result).toEqual({ data: "test response" });
|
||||||
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
expect(mockTransactionClient.$executeRaw).toHaveBeenCalledWith(
|
||||||
expect.arrayContaining(["SET LOCAL app.current_user_id = ", ""]),
|
expect.arrayContaining(["SELECT set_config('app.current_user_id', ", ", true)"]),
|
||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -111,13 +111,13 @@ describe("RlsContextInterceptor", () => {
|
|||||||
// Check that user context was set
|
// Check that user context was set
|
||||||
expect(mockTransactionClient.$executeRaw).toHaveBeenNthCalledWith(
|
expect(mockTransactionClient.$executeRaw).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
expect.arrayContaining(["SET LOCAL app.current_user_id = ", ""]),
|
expect.arrayContaining(["SELECT set_config('app.current_user_id', ", ", true)"]),
|
||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
// Check that workspace context was set
|
// Check that workspace context was set
|
||||||
expect(mockTransactionClient.$executeRaw).toHaveBeenNthCalledWith(
|
expect(mockTransactionClient.$executeRaw).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
expect.arrayContaining(["SET LOCAL app.current_workspace_id = ", ""]),
|
expect.arrayContaining(["SELECT set_config('app.current_workspace_id', ", ", true)"]),
|
||||||
workspaceId
|
workspaceId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,12 +100,12 @@ export class RlsContextInterceptor implements NestInterceptor {
|
|||||||
this.prisma
|
this.prisma
|
||||||
.$transaction(
|
.$transaction(
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
// Set user context (always present for authenticated requests)
|
// Use set_config(..., true) so values are transaction-local and parameterized safely.
|
||||||
await tx.$executeRaw`SET LOCAL app.current_user_id = ${userId}`;
|
// Direct SET LOCAL with bind parameters produces invalid SQL on PostgreSQL.
|
||||||
|
await tx.$executeRaw`SELECT set_config('app.current_user_id', ${userId}, true)`;
|
||||||
|
|
||||||
// Set workspace context (if present)
|
|
||||||
if (workspaceId) {
|
if (workspaceId) {
|
||||||
await tx.$executeRaw`SET LOCAL app.current_workspace_id = ${workspaceId}`;
|
await tx.$executeRaw`SELECT set_config('app.current_workspace_id', ${workspaceId}, true)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate the transaction client via AsyncLocalStorage
|
// Propagate the transaction client via AsyncLocalStorage
|
||||||
|
|||||||
@@ -151,3 +151,56 @@ Regression test added:
|
|||||||
|
|
||||||
- File: `apps/api/src/auth/auth.service.spec.ts`
|
- File: `apps/api/src/auth/auth.service.spec.ts`
|
||||||
- `should preserve raw cookie token value without URL re-encoding`
|
- `should preserve raw cookie token value without URL re-encoding`
|
||||||
|
|
||||||
|
## Deploy + Live Repro (after auth cookie fix deploy)
|
||||||
|
|
||||||
|
Deployment actions executed:
|
||||||
|
|
||||||
|
1. Pushed auth cookie fix commit to `develop`.
|
||||||
|
2. Waited for Woodpecker pipeline success (`mosaic/stack`, build `#514`).
|
||||||
|
3. On `10.1.1.90`:
|
||||||
|
- Ran `/home/localadmin/mosaic/pull_all.sh`.
|
||||||
|
- Updated swarm services to `:dev` images:
|
||||||
|
- `stack_api`
|
||||||
|
- `stack_web`
|
||||||
|
- `stack_coordinator`
|
||||||
|
- `stack_orchestrator`
|
||||||
|
- Verified service convergence.
|
||||||
|
|
||||||
|
Post-deploy behavior:
|
||||||
|
|
||||||
|
- Initial `/auth/session` without cookies now returns `401` (expected).
|
||||||
|
- OAuth callback succeeds and sets BetterAuth session cookie.
|
||||||
|
- `/auth/session` still fails after callback, now due to a new backend `500`.
|
||||||
|
|
||||||
|
## New Root Cause Discovered (RLS interceptor SQL)
|
||||||
|
|
||||||
|
Live `stack_api` logs showed:
|
||||||
|
|
||||||
|
- Auth guard successfully finds session cookie:
|
||||||
|
- `Session cookie found: __Secure-better-auth.session_token`
|
||||||
|
- Then failure inside RLS setup:
|
||||||
|
- PostgreSQL `42601` syntax error at or near `$1`
|
||||||
|
- Source: `RlsContextInterceptor` raw SQL while setting context vars
|
||||||
|
- Request ends as `500 Request processing failed` on `/auth/session`
|
||||||
|
|
||||||
|
Cause:
|
||||||
|
|
||||||
|
- `SET LOCAL app.current_user_id = ${userId}` became `SET LOCAL ... = $1` under parameterization.
|
||||||
|
- PostgreSQL does not accept bind placeholders in `SET` assignment syntax.
|
||||||
|
|
||||||
|
## RLS Fix Applied Locally (pending commit/deploy)
|
||||||
|
|
||||||
|
Files updated:
|
||||||
|
|
||||||
|
- `apps/api/src/common/interceptors/rls-context.interceptor.ts`
|
||||||
|
- Replaced `SET LOCAL` statements with parameter-safe, transaction-local calls:
|
||||||
|
- `SELECT set_config('app.current_user_id', ${userId}, true)`
|
||||||
|
- `SELECT set_config('app.current_workspace_id', ${workspaceId}, true)`
|
||||||
|
- Keeps transaction scoping (`true` => local to transaction).
|
||||||
|
|
||||||
|
- `apps/api/src/common/interceptors/rls-context.interceptor.spec.ts`
|
||||||
|
- Updated expected SQL template fragments to `set_config(...)`.
|
||||||
|
|
||||||
|
- `apps/api/src/common/interceptors/rls-context.integration.spec.ts`
|
||||||
|
- Updated integration expectations to `set_config(...)`.
|
||||||
|
|||||||
Reference in New Issue
Block a user