feat(web-next): resolve the session user + live compliance context
The tRPC createContext was () => ({}) — the mounted dsr/consent routers
401'd every call and the dsr singleton stub threw (audit finding A11).
createTrpcContext now accepts an app resolveUser hook; web-next resolves
the session cookie through the auth feature's validateSession (denylist
included) plus a role snapshot, and threads bindProductionDsr/Consent
(or dev-seed) bindings into every request. The dsr router resolves its
binding from ctx.dsrBinding per request instead of a throwing proxy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -295,15 +295,40 @@ describe("dsrRouter subject scoping (A1 — IDOR)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("dsrRouter singleton guard", () => {
|
||||
it("throws when procedures are called without a real DsrBinding", async () => {
|
||||
// The singleton uses a Proxy that throws on any binding property access.
|
||||
// Procedures access binding lazily, so the Proxy error surfaces at call time.
|
||||
describe("dsrRouter singleton (context-time binding, A11)", () => {
|
||||
it("fails loudly when neither ctx.dsrBinding nor a creation binding exists", async () => {
|
||||
const caller = dsrRouter.createCaller({
|
||||
user: authenticatedUser,
|
||||
} as Record<string, unknown>);
|
||||
await expect(
|
||||
caller.export({ subjectId: "alice", format: "json" }),
|
||||
).rejects.toThrow(/dsrRouter singleton/);
|
||||
).rejects.toMatchObject({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: expect.stringContaining("DsrBinding missing"),
|
||||
});
|
||||
});
|
||||
|
||||
it("serves requests when the app provides ctx.dsrBinding", async () => {
|
||||
const binding = makeBinding();
|
||||
const caller = dsrRouter.createCaller({
|
||||
user: authenticatedUser,
|
||||
dsrBinding: binding,
|
||||
} as Record<string, unknown>);
|
||||
const result = await caller.export({ subjectId: "alice", format: "json" });
|
||||
expect(result.subjectId).toBe("alice");
|
||||
expect(binding.dataExport.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("prefers ctx.dsrBinding over the creation-time binding", async () => {
|
||||
const creationBinding = makeBinding();
|
||||
const ctxBinding = makeBinding();
|
||||
const router = createDsrRouter(creationBinding as unknown as DsrBinding);
|
||||
const caller = router.createCaller({
|
||||
user: authenticatedUser,
|
||||
dsrBinding: ctxBinding,
|
||||
} as Record<string, unknown>);
|
||||
await caller.export({ subjectId: "alice", format: "json" });
|
||||
expect(ctxBinding.dataExport.calls).toHaveLength(1);
|
||||
expect(creationBinding.dataExport.calls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user