Skip to main content

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

HelperSignaturePurpose
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_MAPRecord<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 }
  • ip from req.ip, x-forwarded-for, or x-real-ip (in that order)
  • userAgent from user-agent header
  • timestamp is Date.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