generic helpers
Framework-agnostic helpers - IAM_METHOD_ACTION_MAP, createSubjectCan, extractEnvironment, generatePermissionMap. Use across runtimes or for custom integrations.
Install
import {
IAM_METHOD_ACTION_MAP,
createSubjectCan,
iamDefaultCsrfCheck,
extractEnvironment,
generatePermissionMap,
} from '@gentleduck/iam/server/generic'
Use the generic helpers when the framework wrappers are too opinionated, or when you want the same access-control logic across multiple runtimes.
API
| Helper | Signature | Purpose |
|---|---|---|
generatePermissionMap | (engine, subjectId, checks, environment?) | Wraps engine.permissions() for server-to-client hydration |
createSubjectCan | (engine, subjectId, environment?) | Returns a subject-bound can(action, resource, resourceId?, scope?) |
extractEnvironment | (req) | Builds the default { ip, userAgent, timestamp } from common request shapes |
iamDefaultCsrfCheck | (req) -> boolean | Built-in Sec-Fetch-Site predicate the admin routers use by default (SEC-103) |
IAM_METHOD_ACTION_MAP | Record<string, string> | Read-only CRUD map (GET -> read, POST -> create, etc.) used by every built-in integration |
Bind a subject
createSubjectCan is the workhorse - bind a user once, then run multiple checks without repeating the subject ID:
import { createSubjectCan, extractEnvironment } from '@gentleduck/iam/server/generic'
const can = createSubjectCan(engine, userId, extractEnvironment(req))
if (await can('delete', 'post', req.params.postId)) {
await deletePost(req.params.postId)
}
if (await can('read', 'analytics', undefined, 'org-1')) {
return renderAnalytics()
}
Useful for handlers, background jobs, service objects, and tests.
Generate permission maps
generatePermissionMap is a thin wrapper over engine.permissions() that fits server-to-client hydration patterns:
const permissions = await generatePermissionMap(engine, userId, [
{ action: 'create', resource: 'post' },
{ action: 'manage', resource: 'team', scope: 'org-1' },
])
// Pass to client
return new Response(JSON.stringify({ permissions }), {
headers: { 'content-type': 'application/json' },
})
The result is a Record<string, boolean> keyed by ${scope}:${action}:${resource} (scope omitted when undefined). Hydrate it with the client libraries.
Default environment
extractEnvironment reads common headers from any request shape with headers (object or Headers) and an optional ip:
const env = extractEnvironment(req)
// -> { ip, userAgent, timestamp }
ipfromreq.ip,x-forwarded-for, orx-real-ip(in that order)userAgentfromuser-agentheadertimestampisDate.now()
Override anywhere a getEnvironment option is exposed by passing your own extractor.
Default CSRF predicate
iamDefaultCsrfCheck is the Sec-Fetch-Site predicate every admin router uses
by default (SEC-103 / CAVEAT-2). It handles all four request shapes
(Express/Nest record headers, fetch-API Headers.get, Hono c.req.header).
Returns true when the request is allowed, false when it must be rejected.
import { iamDefaultCsrfCheck } from '@gentleduck/iam/server/generic'
// Compose with your own check - accept default behaviour AND require Origin.
const allow = new Set(['https://admin.example.com'])
const csrfCheck = (req: Request) =>
iamDefaultCsrfCheck(req) && allow.has(req.headers.get('origin') ?? '')
Sec-Fetch-Site is populated by every modern browser; non-browser callers
(curl, server-to-server) omit it and pass - they must be gated by bearer /
mTLS auth at a layer the admin router can trust.
When to use
- Multiple runtimes share auth logic (Edge + Node + Worker)
- Building a custom framework integration not covered by the four built-in wrappers
- Background jobs / queue consumers that need permission checks
- Tests where you want a quick
can()without setting up middleware