generic helpers
Framework-agnostic helpers — METHOD_ACTION_MAP, createSubjectCan, extractEnvironment, generatePermissionMap. Use across runtimes or for custom integrations.
Install
import {
METHOD_ACTION_MAP,
createSubjectCan,
extractEnvironment,
generatePermissionMap,
} from '@gentleduck/iam/server/generic'import {
METHOD_ACTION_MAP,
createSubjectCan,
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 |
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()
}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' },
})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 }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.
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