Core Concepts
Relevant source files
The following files were used as context for generating this wiki page:
@joindeed/prisma-auth provides a declarative authorization layer for Prisma models. It moves authorization logic from the GraphQL resolver level to the Prisma model level by using annotations in the Prisma schema. The system validates access through three primary middlewares that intercept field-level, type-level, and list-level operations.
Sources: README.md:3-8, src/index.ts:40-44
Authorization Annotations
Authorization rules are defined directly in the schema.prisma file using triple-slash comments. These comments are parsed to determine which roles have access to specific models or fields.
- Model Level: Defines access for the entire entity.
- Field Level: Restricts access to specific fields within a model.
/// @Auth(read: [Owner(privileges: [x,y,z]), Admin])
model Purchases {
id String @id
/// @Auth(read:[ Admin ])
email String
}
Sources: README.md:27-42, src/descriptionToRoles.ts:16-25
The Parser
The system uses a PEG.js-generated parser to translate @Auth strings into structured role objects. It extracts the operation type (currently supporting read) and the associated roles with their optional arguments.
Sources: src/generated/parser.ts:1-20, src/descriptionToRoles.ts:1-14
Role Matching and Constraints
The authorization system relies on a configuration object that defines how roles are evaluated. Each role consists of matchers for individual records and query constraints for list operations.
Configuration Structure
| Property | Type | Description |
|---|---|---|
globalRoles | Record<string, Role> | Roles applicable across all models. |
rolesPerType | RolesPerType | Model-specific overrides for role matchers. |
defaultFields | Record<string, Function> | Fields to automatically select when specific fields are requested. |
Sources: src/index.ts:17-31, README.md:46-70
Role Definition
A Role contains three main components:
- matcher: A function returning a boolean to authorize access to a specific record.
- queryConstraint: A function returning a Prisma
whereclause to filter lists. - matcherDependenciesSelect: Defines fields required from the database to execute the matcher.
Sources: src/index.ts:9-13, README.md:72-76
Authorization Architecture
The system operates by wrapping GraphQL resolvers and modifying Prisma queries before execution.
The diagram shows how user requests pass through middlewares to extract authorization rules and apply constraints to the Prisma context.
The Three Middlewares
- Field Constraint Middleware: Validates if the current user can read a specific field on a specific record. If denied, it patches the response (e.g., returning
nullor""). - Type Constraint Middleware: Validates access to 1-to-1 relations by checking the authorization rules of the related type.
- List Constraint Middleware: Injects
whereandselectclauses into Prisma queries via thewithAuthhelper to ensure users only see records they are permitted to access.
Sources: src/index.ts:40-44, src/makeFieldConstraintMiddleware.ts:20-30, src/makeTypeConstraintMiddleware.ts:31-40, src/makeListConstraintMiddleware.ts:25-35
Query Interception and Filtering
The system modifies outgoing Prisma queries to enforce security. This is primarily handled by the PrismaSelect class and the getListWhereConstrains utility.
Dynamic Select Injection
The PrismaSelect class (forked from prisma-tools) analyzes the GraphQL info object. It automatically adds fields required by role matchers to the Prisma select block. This ensures that when a matcher runs, the data it needs (like a userId or ownerId) is present in the record.
Sources: src/select.ts:42-55, src/getMatcherDependenciesSelect.ts:11-20
Automated Where Clauses
When list fields are queried, the getListWhereConstrains function aggregates all applicable role constraints into a single Prisma AND block containing an OR of all allowed role conditions.
The sequence diagram illustrates how withAuth intercepts the query parameters to apply authorization filters.
Sources: src/getListWhereConstrains.ts:30-45, src/makeListConstraintMiddleware.ts:40-55
Security Safeguards
Prototype Pollution Protection
The PrismaSelect.mergeDeep method explicitly blocks dangerous keys such as __proto__, constructor, and prototype to prevent prototype pollution during the merging of query objects.
Sources: src/select.ts:120-135, src/select.test.ts:6-25
Short-Circuiting
If a model has @Auth rules defined but the user matches no roles or no constraints are provided, the system injects an alwaysFalseCondition (querying for a non-existent ID) to ensure no data is leaked.
Sources: src/getListWhereConstrains.ts:20-25, src/getListWhereConstrains.ts:62-65
Summary
@joindeed/prisma-auth centralizes authorization logic in the Prisma schema, reducing the risk of security holes in complex GraphQL relations. It uses specialized middlewares to enforce field, type, and list-level security by dynamically rewriting Prisma queries and validating records against defined roles.