Securing Arbitrary Prisma Queries
Relevant source files
The following files were used as context for generating this wiki page:
The @joindeed/prisma-auth library provides a declarative authorization layer that operates at the Prisma model level. It allows developers to define access control rules directly in the Prisma schema using triple-slash comments (e.g., /// @Auth(...)). While primarily used to secure GraphQL resolvers, the system offers a robust mechanism for securing arbitrary Prisma queries through the withAuth utility, ensuring that even complex nested relations and aggregate operations respect defined security constraints.
Sources: README.md:3-15, src/makeListConstraintMiddleware.ts:13-18
Core Architecture and Data Flow
The authorization system relies on GraphQL middleware to intercept requests and inject security constraints into the Prisma client's query arguments. The makeListConstraintMiddleware component is responsible for enforcing these constraints on list queries and nested relations.
Authorization Flow
The following diagram illustrates how authorization constraints are extracted from the schema and applied to a query:
The withAuth Utility
The withAuth function is the primary interface for applying security constraints to arbitrary Prisma queries. It is injected into the GraphQL context and should be wrapped around any Prisma call that requires authorization.
Usage Patterns
| Pattern | Purpose | Description |
|---|---|---|
| Standard Query | Root Return Type | Applied directly to queries like findMany or findUnique where the result matches the resolver's return type. |
| Path-Based | Composite Types | Used when the resolver returns a custom object containing Prisma entities. Requires a path (e.g., path.toUser) and a type (e.g., UserType). |
| Arbitrary Type | Isolated Queries | Used for queries unrelated to the GraphQL return type, such as count(). Only generates the where constraint. |
Sources: README.md:95-128, src/makeListConstraintMiddleware.ts:40-60
Security Injection Logic
withAuth ensures that authorization constraints cannot be overridden by user-provided parameters by merging the auth-derived where and select clauses last.
Query Constraints and Filtering
The library generates Prisma where clauses based on queryConstraint functions defined in the configuration. These constraints are applied to root-level list queries and nested list relations.
Boolean Short-Circuiting
To ensure security by default, the system uses "always true" and "always false" conditions to handle role matching results.
- alwaysTrueCondition: Used when a role (like
Admin) is granted full access. Implementation:id: { not: { equals: 'ffffffffffffffffffffffff' } }. - alwaysFalseCondition: Used when no roles match, effectively returning zero results. Implementation:
id: { equals: 'ffffffffffffffffffffffff' }.
Sources: src/getListWhereConstrains.ts:6-25, src/getListWhereConstrains.test.ts:3-11
Logic Grouping
Multiple role constraints are combined using an OR logic, which is then wrapped in an AND block to prevent interference with user-defined filters.
Sources: src/getListWhereConstrains.ts:51-65
Field Requirements and Select Deep Merging
Authorization logic often depends on specific fields being present in the record (e.g., a userId to match an Owner role). The PrismaSelect plugin automatically adds these dependencies to the Prisma select clause.
Required Fields Resolution
- Parse: The system parses
@Authannotations to identify required roles. - Lookup: It looks up
matcherDependenciesSelectfor those roles in theConfiguration. - Merge: It merges these fields into the final Prisma query using
PrismaSelect.mergeDeep.
Sources: src/getMatcherDependenciesSelect.ts:10-33, src/select.ts:210-235
Prototype Pollution Protection
The mergeDeep function includes explicit protections against prototype pollution by blocking dangerous keys: __proto__, constructor, and prototype.
Sources: src/select.ts:133-150, src/select.test.ts:5-75
Implementation Summary
Securing arbitrary Prisma queries involves applying the withAuth wrapper to Prisma client calls. This wrapper leverages middlewares to parse schema annotations and configuration roles, transforming them into native Prisma filtering and selection arguments.
Sources: README.md:95-101, src/index.ts:44-50