oRPC
permdock/orpc adds a request-scoped PermDock to oRPC context, a protect middleware fed by procedure input, and an oo.spec hook that emits OpenAPI 3.2 security for the generated document.
Status: planned Phase: 2
Purpose
oRPC is contract-first and generates OpenAPI natively, which makes it the RPC framework where the OpenAPI 3.2 story is most direct. permdock/orpc mirrors the tRPC adapter: ctx.permdock on every procedure, protect(permission, load) as a middleware after input validation, ORPCError denials carrying Problem Details, and an oo.spec(middleware, ...) hook so the security requirement is attached to the operation wherever the middleware is used.
API
// server/permdock.ts
import { os } from '@orpc/server'
import { oo } from '@orpc/openapi'
import { createPermDock } from 'permdock/orpc'
import { policy } from './policy'
import { permissions } from './permissions'
export const { permdock, protect, openapi } = createPermDock(policy, { subject: ({ context }) => context.user ?? null })
export const base = os.$context<Context>().use(permdock()) // context.permdock is the request-scoped PermDock
// server/routers/posts.ts
export const remove = base
.route({ method: 'DELETE', path: '/posts/{id}' })
.input(z.object({ id: z.string() }))
.use(openapi.protect(permissions.post.delete, ({ input }) => loadPost(input.id))) // protect + oo.spec in one
.handler(async ({ context }) => deletePost(context.permdockData))
export const list = base
.route({ method: 'GET', path: '/posts' })
.handler(async ({ context }) => context.permdock.filter(permissions.post.read, await listPosts()))| Export | Role |
|---|---|
permdock() | Middleware. Resolves the subject once per request (memoised on the request context) and adds permdock to the context with oRPC's context inference. |
protect(permission, load?) | Middleware placed after .input(...). Loads and boundary-validates the resource, decides, continues with context.permdockData or throws ORPCError with code FORBIDDEN. |
openapi.protect(permission, load?) | protect wrapped with oo.spec(middleware, (spec) => ...) so the operation's security requirement and x-permdock-permissions extension are merged into the generated document automatically. |
openapi.securitySchemes() | Document-level securitySchemes (scopes from listPermissions, oauth2MetadataUrl, deviceAuthorization, deprecated) to pass to OpenAPIGenerator. Targets 3.2; 3.1 output uses the registered x-oai-* fallbacks and x-permdock-oauth2MetadataUrl (see OpenAPI registries). |
permdockHandler | Procedure or Fetch handler for the AuthZEN-shaped decision endpoint. |
Request lifecycle
- The oRPC handler (Fetch, Node, Next.js) creates the initial context with the authenticated user.
permdock()resolves the subject once and adds the instance to the context.protect(oropenapi.protect) runs after input validation: load, validate, decide, continue or throw.- Handlers call
context.permdock.assert(...),filterorwhere; thrown PermDock errors are converted toORPCErrorby an interceptor the adapter provides. - When
OpenAPIGeneratorruns,oo.speccontributions from everyopenapi.protectusage appear on their operations.
What it validates
- Untrusted loader results and inputs used as resource data against the resource schema under
validate: 'boundary', after oRPC's own input validation. - Decision-endpoint bodies against the AuthZEN
evaluationsschema. - Middleware order at the type level:
protectrequirespermdockin the inferred context.
How denials surface
denied:ORPCErrorcodeFORBIDDEN, status403,datacarrying the Problem Details object. Through the OpenAPI handler the response body isapplication/problem+json.approval-required:FORBIDDENwithdata.typeending in/approval-requiredanddata.token.- Boundary validation failure:
BAD_REQUESTwithissues. - Anonymous subject:
UNAUTHORIZED.
Example app
apps/examples/orpc: oRPC on Hono with a posts contract, permdock() and openapi.protect on mutations, OpenAPIGenerator output checked into the example and validated by permdock openapi --check, a React client using permdock/react against the shared decision endpoint, and Vitest tests calling procedures directly and through the OpenAPI handler.
Related standards
- OpenAPI 3.2, decision 0014.
- Problem Details.
- AuthZEN for the decision endpoint.
Open questions
- Whether
openapi.protectshould be the only exported guard (always emitting spec) or stay separate fromprotectfor apps that do not generate OpenAPI. - How
context.permdockDatainteracts with oRPC's typed context merging in nested routers; the alternative is aprotect(permission, load, handler)shape. - Whether the Problem Details body should be the
ORPCErrordatapayload or whether the OpenAPI handler should own the content type mapping.
tRPC
permdock/trpc adds a request-scoped PermDock to tRPC context and a protect middleware that loads the resource from procedure input, with a trpc-to-openapi hook for OpenAPI security.
MCP
permdock/mcp guards MCP tools with typed permissions, scope step-up challenges, per-caller tool lists, boundary-validated arguments and model-readable refusals.