npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alfredaoo/auth-policies

v1.0.0

Published

A flexible, framework-agnostic authorization library with policy-based access control

Readme

@alfredaoo/auth-policies

A flexible, framework-agnostic authorization library with policy-based access control (RBAC).

Features

  • Policy-based authorization - Define granular access rules per resource type
  • Role-permission mapping - Simple role to permissions configuration
  • Permission inheritance - Define role hierarchies with automatic permission inheritance
  • Multiple roles - Users can have multiple roles with merged permissions
  • Multi-tenancy - Different roles per organization/tenant with tenant-scoped permissions
  • Context-aware checks - Pass resources for dynamic authorization decisions
  • Framework-agnostic - Works with any Node.js application
  • React hooks - useAuth, usePermission, <Can> components for React apps
  • Next.js integration - Built-in helpers for Next.js applications
  • Caching - LRU cache with TTL for policy check results
  • Audit logging - Track all authorization decisions
  • TypeScript-first - Full type safety and IntelliSense support
  • Lightweight - Zero dependencies

Installation

npm install @alfredaoo/auth-policies
# or
pnpm add @alfredaoo/auth-policies
# or
yarn add @alfredaoo/auth-policies

Quick Start

1. Define your roles and permissions

// lib/authorization/role-permissions.ts
export const rolePermissions = {
  SUPER_ADMIN: [
    'view.user', 'viewAll.user', 'create.user', 'update.user', 'delete.user',
    'view.instance', 'viewAll.instance', 'create.instance', 'update.instance', 'delete.instance',
  ],
  ADMIN: [
    'view.instance', 'viewAll.instance', 'create.instance', 'update.instance', 'delete.instance',
  ],
  OPERATOR: [
    'view.instance', 'viewAll.instance',
  ],
} as const

export type Role = keyof typeof rolePermissions
export type Permission = (typeof rolePermissions)[Role][number]

2. Define your user type

// lib/authorization/types.ts
import type { BaseUser } from '@alfredao/auth-policies'
import type { Role } from './role-permissions'

export interface AuthorizedUser extends BaseUser<Role> {
  id: string
  role: Role
  email: string
  name: string
}

3. Create policies for your resources

// lib/policies/user-policy.ts
import type { AuthorizedUser } from '../authorization/types'
import { hasPermission } from '../authorization'

export const UserPolicy = {
  async view(user: AuthorizedUser, resource?: User) {
    // Users can always view themselves
    if (resource && user.id === resource.id) return true
    return hasPermission(user, 'view.user')
  },

  async viewAll(user: AuthorizedUser) {
    return hasPermission(user, 'viewAll.user')
  },

  async update(user: AuthorizedUser, resource?: User) {
    // Users can update themselves
    if (resource && user.id === resource.id) return true
    // Admins cannot modify super admins
    if (user.role === 'ADMIN' && resource?.role === 'SUPER_ADMIN') return false
    return hasPermission(user, 'update.user')
  },

  async delete(user: AuthorizedUser, resource?: User) {
    // Users cannot delete themselves
    if (resource && user.id === resource.id) return false
    return hasPermission(user, 'delete.user')
  },
}

4. Create the authorization instance

// lib/authorization/index.ts
import { createAuth } from '@alfredao/auth-policies'
import { redirect } from 'next/navigation'
import { rolePermissions, type Role } from './role-permissions'
import type { AuthorizedUser } from './types'
import { UserPolicy } from '../policies/user-policy'
import { InstancePolicy } from '../policies/instance-policy'

// Resource types in your application
type ResourceType = 'User' | 'Instance'

export const auth = createAuth<AuthorizedUser, Role, ResourceType>({
  rolePermissions,
  policies: {
    User: UserPolicy,
    Instance: InstancePolicy,
  },
  getUser: async () => {
    // Your auth logic here (e.g., get session from cookies)
    const session = await getSession()
    return session?.user ?? null
  },
  handlers: {
    onUnauthorizedRedirect: () => redirect('/unauthorized'),
  },
})

// Re-export utilities for convenience
export const { can, canApi, hasPermission, requireUser } = auth

5. Use in your application

// API Route
import { canApi } from '@/lib/authorization'

export async function PUT(request: Request, { params }: { params: { id: string } }) {
  const user = await prisma.user.findUnique({ where: { id: params.id } })

  await canApi('update', 'User', { resource: user })

  // ... update logic
}

// Server Component
import { can } from '@/lib/authorization'

export default async function UsersPage() {
  await can('viewAll', 'User')

  const users = await prisma.user.findMany()
  return <UserList users={users} />
}

API Reference

createAuth(config)

Creates an authorization instance with the provided configuration.

const auth = createAuth({
  rolePermissions: Record<Role, Permission[]>,
  policies: PolicyMap,
  getUser: () => Promise<User | null>,
  handlers?: {
    onUnauthorizedRedirect?: (message: string) => never,
    onUnauthorizedThrow?: (message: string) => never,
  },
})

Returns:

  • can(action, type, options?) - For server components (redirects on failure)
  • canApi(action, type, options?) - For API routes (throws on failure)
  • checkPermission(action, type, options?) - Returns boolean, never throws
  • hasPermission(user, permission) - Check single permission
  • hasAnyPermission(user, permissions) - Check if user has any of the permissions
  • hasAllPermissions(user, permissions) - Check if user has all permissions
  • requireUser() - Get user or throw UnauthenticatedException
  • getUser() - Get user or null

createNextAuth(config)

Convenience wrapper for Next.js applications.

import { createNextAuth } from '@alfredao/auth-policies'
import { redirect } from 'next/navigation'

const auth = createNextAuth({
  rolePermissions,
  policies,
  getUser,
  redirect,  // Pass Next.js redirect function
  redirectTo: '/unauthorized',  // Optional, defaults to '/unauthorized'
})

Exceptions

import { UnauthorizedException, UnauthenticatedException } from '@alfredaoo/auth-policies'

// In your API error handler
function handleApiError(error: unknown) {
  if (error instanceof UnauthorizedException) {
    return new Response(error.message, { status: 403 })
  }
  if (error instanceof UnauthenticatedException) {
    return new Response(error.message, { status: 401 })
  }
  // ... handle other errors
}

React Hooks

The library provides React hooks and components for client-side authorization.

Setup

Wrap your app with AuthProvider:

// app/providers.tsx
'use client'

import { AuthProvider } from '@alfredaoo/auth-policies/react'
import { auth } from '@/lib/authorization'

export function Providers({ children, user }: { children: React.ReactNode; user: User | null }) {
  return (
    <AuthProvider auth={auth} initialUser={user}>
      {children}
    </AuthProvider>
  )
}

useAuth

Access the full auth context:

import { useAuth } from '@alfredaoo/auth-policies/react'

function UserMenu() {
  const { user, isLoading, isAuthenticated, refresh } = useAuth()

  if (isLoading) return <Spinner />
  if (!isAuthenticated) return <LoginButton />

  return <div>Welcome, {user.name}</div>
}

useUser

Simplified hook for user data:

import { useUser } from '@alfredaoo/auth-policies/react'

function Profile() {
  const { user, isAuthenticated } = useUser()

  if (!isAuthenticated) return <LoginPrompt />
  return <div>{user.email}</div>
}

usePermission

Check a single permission reactively:

import { usePermission } from '@alfredaoo/auth-policies/react'

function DeleteButton({ postId }: { postId: string }) {
  const { allowed, isLoading } = usePermission('delete', 'Post')

  if (isLoading) return <button disabled>...</button>
  if (!allowed) return null

  return <button onClick={() => deletePost(postId)}>Delete</button>
}

usePermissions

Check multiple permissions at once:

import { usePermissions } from '@alfredaoo/auth-policies/react'

function AdminPanel() {
  const { permissions, isLoading } = usePermissions([
    { action: 'create', resourceType: 'User' },
    { action: 'delete', resourceType: 'User' },
  ])

  if (isLoading) return <Spinner />

  return (
    <div>
      {permissions['create:User'] && <CreateUserButton />}
      {permissions['delete:User'] && <DeleteUserButton />}
    </div>
  )
}

Can Component

Declarative conditional rendering:

import { Can } from '@alfredaoo/auth-policies/react'

function PostActions({ post }: { post: Post }) {
  return (
    <div>
      {/* Basic usage */}
      <Can action="delete" resourceType="Post">
        <DeleteButton postId={post.id} />
      </Can>

      {/* With fallback */}
      <Can action="update" resourceType="Post" fallback={<span>Read only</span>}>
        <EditButton postId={post.id} />
      </Can>

      {/* With loading state */}
      <Can action="delete" resourceType="Post" loading={<Spinner />}>
        <DeleteButton postId={post.id} />
      </Can>

      {/* Context-aware check */}
      <Can action="update" resourceType="Post" resource={post}>
        <EditButton postId={post.id} />
      </Can>
    </div>
  )
}

Cannot Component

Render content when permission is denied:

import { Cannot } from '@alfredaoo/auth-policies/react'

function PostView({ post }: { post: Post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <Cannot action="update" resourceType="Post">
        <p className="text-gray-500">You don't have permission to edit this post.</p>
      </Cannot>
    </div>
  )
}

Middleware

The library provides middleware helpers for API route protection.

Next.js App Router

Wrap API route handlers with authorization:

// lib/authorization/middleware.ts
import { createAuthMiddleware } from '@alfredaoo/auth-policies/middleware'

export const withAuth = createAuthMiddleware({
  rolePermissions,
  policies,
  getUser: async () => {
    const session = await getSession()
    return session?.user ?? null
  },
})
// app/api/posts/[id]/route.ts
import { withAuth } from '@/lib/authorization/middleware'

export const DELETE = withAuth(
  async (request, { params }) => {
    await deletePost(params.id)
    return Response.json({ success: true })
  },
  { action: 'delete', type: 'Post' }
)

// With context-aware check
export const PUT = withAuth(
  async (request, { params }) => {
    const data = await request.json()
    const post = await updatePost(params.id, data)
    return Response.json(post)
  },
  {
    action: 'update',
    type: 'Post',
    getResource: async (req) => {
      const url = new URL(req.url)
      const id = url.pathname.split('/').pop()
      return await getPost(id)
    },
  }
)

Permission-Based Middleware

Simpler middleware that checks permissions directly:

import { createPermissionMiddleware } from '@alfredaoo/auth-policies/middleware'

const requirePermission = createPermissionMiddleware({
  rolePermissions,
  getUser: async () => getSession()?.user ?? null,
})

export const DELETE = requirePermission(
  async (request) => {
    return Response.json({ success: true })
  },
  { permission: 'delete.post' }
)

Express / Hono

Compatible middleware for Express, Hono, and similar frameworks:

import express from 'express'
import { createExpressAuth } from '@alfredaoo/auth-policies/middleware'

const app = express()

const { protect, requireAuth, requirePermission } = createExpressAuth({
  rolePermissions,
  policies,
  getUser: (req) => req.user, // From your auth middleware
})

// Require authentication only
app.get('/profile', requireAuth, (req, res) => {
  res.json(req.user)
})

// Policy-based protection
app.delete('/posts/:id', protect({ action: 'delete', type: 'Post' }), (req, res) => {
  res.json({ success: true })
})

// Permission-based protection
app.post('/posts', requirePermission('create.post'), (req, res) => {
  res.json({ success: true })
})

// With resource for context-aware check
app.put('/posts/:id',
  protect({
    action: 'update',
    type: 'Post',
    getResource: async (req) => await getPost(req.params.id),
  }),
  (req, res) => {
    res.json({ success: true })
  }
)

Audit Logging

Track all authorization decisions for security monitoring and compliance:

import { createAuth, type AuditLogEntry } from '@alfredaoo/auth-policies'

const auth = createAuth({
  rolePermissions,
  policies,
  getUser,
  onAudit: async (entry: AuditLogEntry) => {
    // Log to your preferred logging system
    await logger.info('Authorization check', {
      timestamp: entry.timestamp,
      userId: entry.user?.id,
      role: entry.user?.role,
      action: entry.action,
      resourceType: entry.resourceType,
      allowed: entry.allowed,
      reason: entry.reason,
      duration: entry.duration,
      metadata: entry.metadata,
    })
  },
})

Audit Entry Structure

interface AuditLogEntry {
  timestamp: Date           // When the check occurred
  user: User | null         // The user (null if unauthenticated)
  action: string            // The action being performed
  resourceType: string      // The resource type
  allowed: boolean          // Whether access was granted
  reason?: string           // Denial reason: 'unauthenticated' | 'policy_denied' | 'policy_not_found' | 'action_not_found'
  resource?: unknown        // The resource being accessed
  duration: number          // Policy check duration in ms
  metadata?: Record<string, unknown>  // Custom metadata
}

Adding Metadata

Pass additional context for audit logs:

// Include request metadata
await auth.canApi('delete', 'Post', {
  resource: post,
  metadata: {
    ip: request.ip,
    userAgent: request.headers['user-agent'],
    requestId: request.id,
  },
})

Integration Examples

Winston:

import winston from 'winston'

const logger = winston.createLogger({ /* ... */ })

const auth = createAuth({
  // ...
  onAudit: (entry) => {
    logger.info('auth', entry)
  },
})

Pino:

import pino from 'pino'

const logger = pino()

const auth = createAuth({
  // ...
  onAudit: (entry) => {
    logger.info(entry, 'authorization check')
  },
})

Database:

const auth = createAuth({
  // ...
  onAudit: async (entry) => {
    await prisma.auditLog.create({
      data: {
        timestamp: entry.timestamp,
        userId: entry.user?.id,
        action: entry.action,
        resourceType: entry.resourceType,
        allowed: entry.allowed,
        reason: entry.reason,
        duration: entry.duration,
        metadata: entry.metadata,
      },
    })
  },
})

Caching

Enable caching to memoize policy check results and improve performance:

const auth = createAuth({
  rolePermissions,
  policies,
  getUser,
  cache: {
    enabled: true,
    ttl: 60000, // 1 minute (default)
    maxSize: 1000, // Max entries (default)
  },
})

Cache Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | boolean | false | Enable or disable caching | | ttl | number | 60000 | Time-to-live in milliseconds. Set to 0 for no expiration | | maxSize | number | 1000 | Maximum cache entries. Uses LRU eviction when exceeded | | getResourceKey | function | Uses resource.id | Custom function to generate cache key for resources |

Cache Keys

Cache keys are generated from: action:resourceType:userId:resourceId

// Without resource: "view:Post:user-123:no-resource"
await auth.can('view', 'Post')

// With resource: "update:Post:user-123:post-456"
await auth.can('update', 'Post', { resource: { id: 'post-456' } })

Custom Resource Key

For resources without an id property, provide a custom key generator:

const auth = createAuth({
  // ...
  cache: {
    enabled: true,
    getResourceKey: (resource) => {
      if (resource && typeof resource === 'object' && 'uuid' in resource) {
        return String(resource.uuid)
      }
      return undefined
    },
  },
})

Cache Invalidation

Invalidate cached entries when permissions or resources change:

// Invalidate all entries for a user (e.g., after role change)
auth.cache?.invalidateUser('user-123')

// Invalidate all entries for a resource type
auth.cache?.invalidateResourceType('Post')

// Invalidate entries for a specific resource
auth.cache?.invalidateResource('Post', 'post-456')

// Clear entire cache
auth.cache?.clear()

Cache Maintenance

For long-running processes, periodically clean up expired entries:

// Clean up expired entries
const removed = auth.cache?.cleanup()
console.log(`Removed ${removed} expired entries`)

// Get cache statistics
const stats = auth.cache?.stats()
console.log(`Cache size: ${stats?.size}/${stats?.maxSize}`)

Audit Logging with Cache

When caching is enabled, audit entries include a cached flag in metadata:

const auth = createAuth({
  // ...
  cache: { enabled: true },
  onAudit: (entry) => {
    if (entry.metadata?.cached) {
      console.log('Result from cache')
    }
  },
})

Standalone Cache

Use the cache independently for custom scenarios:

import { createPolicyCache } from '@alfredaoo/auth-policies'

const cache = createPolicyCache({
  ttl: 30000,
  maxSize: 500,
})

const key = cache.generateKey('view', 'Post', 'user-123', { id: 'post-1' })
cache.set(key, true)
const result = cache.get(key) // true

Context-Aware Policies

Policies can receive the resource being accessed for dynamic authorization:

const InstancePolicy = {
  async delete(user: AuthorizedUser, resource?: Instance) {
    if (!hasPermission(user, 'delete.instance')) return false

    // Can't delete instances that are being provisioned
    if (resource?.status === 'PROVISIONING') return false

    // Can't delete soft-deleted instances
    if (resource?.deleted_at) return false

    return true
  },
}

// Usage
await canApi('delete', 'Instance', { resource: instance })

Standalone Permission Checker

If you only need permission checking without the full policy system:

import { createPermissionChecker } from '@alfredaoo/auth-policies'

const { hasPermission, hasAnyPermission } = createPermissionChecker(rolePermissions)

if (hasPermission(user, 'create.user')) {
  // ...
}

Permission Inheritance

Define role hierarchies where higher roles automatically inherit permissions from lower roles:

Basic Inheritance

const rolePermissions = {
  VIEWER: ['view.user', 'view.post'],
  EDITOR: {
    inherits: 'VIEWER',
    permissions: ['create.post', 'update.post'],
  },
  ADMIN: {
    inherits: 'EDITOR',
    permissions: ['delete.post', 'delete.user'],
  },
}

// Resolved permissions:
// VIEWER: ['view.user', 'view.post']
// EDITOR: ['view.user', 'view.post', 'create.post', 'update.post']
// ADMIN:  ['view.user', 'view.post', 'create.post', 'update.post', 'delete.post', 'delete.user']

Multiple Inheritance

Inherit from multiple roles at once:

const rolePermissions = {
  VIEWER: ['view.dashboard'],
  BILLING: {
    inherits: 'VIEWER',
    permissions: ['view.invoice', 'create.invoice'],
  },
  SUPPORT: {
    inherits: 'VIEWER',
    permissions: ['view.ticket', 'update.ticket'],
  },
  MANAGER: {
    inherits: ['BILLING', 'SUPPORT'],  // Inherits from both
    permissions: ['delete.user'],
  },
}

// MANAGER gets: view.dashboard, view.invoice, create.invoice, view.ticket, update.ticket, delete.user

Mixed Syntax

You can mix flat arrays and inheritance in the same configuration:

const rolePermissions = {
  VIEWER: ['view.user'],  // Flat array
  ADMIN: {                // With inheritance
    inherits: 'VIEWER',
    permissions: ['delete.user'],
  },
}

Circular Inheritance Detection

The library automatically detects and throws an error for circular inheritance:

import { CircularInheritanceError } from '@alfredaoo/auth-policies'

const rolePermissions = {
  A: { inherits: 'B', permissions: ['a'] },
  B: { inherits: 'A', permissions: ['b'] },  // Circular!
}

try {
  createAuth({ rolePermissions, ... })
} catch (error) {
  if (error instanceof CircularInheritanceError) {
    console.log(error.cycle)  // ['A', 'B', 'A']
  }
}

Resolving Permissions Manually

For debugging or advanced use cases, resolve permissions manually:

import { resolvePermissions } from '@alfredaoo/auth-policies'

const resolved = resolvePermissions(rolePermissions)
console.log(resolved.ADMIN)  // All permissions including inherited

Multiple Roles

Users can have multiple roles, and permissions are merged from all roles:

Single Role (Simple)

interface User extends BaseUser<'ADMIN' | 'USER'> {
  id: string
  role: 'ADMIN' | 'USER'  // Single role
}

const user: User = { id: '1', role: 'ADMIN' }

Multiple Roles

interface User extends BaseUser<'VIEWER' | 'EDITOR' | 'BILLING'> {
  id: string
  roles: ('VIEWER' | 'EDITOR' | 'BILLING')[]  // Multiple roles
}

const user: User = {
  id: '1',
  roles: ['VIEWER', 'BILLING'],  // Has permissions from both roles
}

// Permissions are merged from all roles
auth.hasPermission(user, 'view.dashboard')  // true (from VIEWER)
auth.hasPermission(user, 'view.invoice')    // true (from BILLING)
auth.hasPermission(user, 'create.post')     // false (EDITOR only)

Combined (role + roles)

You can use both role and roles together:

const user = {
  id: '1',
  role: 'VIEWER',           // Primary role
  roles: ['BILLING'],       // Additional roles
}

// Gets permissions from VIEWER + BILLING
auth.getPermissions(user)   // ['view.dashboard', 'view.invoice', ...]
auth.getRoles(user)         // ['VIEWER', 'BILLING']

Utility Functions

// Get all roles for a user
const roles = auth.getRoles(user)  // ['VIEWER', 'BILLING']

// Get all permissions (merged from all roles)
const permissions = auth.getPermissions(user)

// Check permissions across all roles
auth.hasPermission(user, 'view.invoice')
auth.hasAnyPermission(user, ['view.invoice', 'create.post'])
auth.hasAllPermissions(user, ['view.dashboard', 'view.invoice'])

Multi-Tenancy

Support for SaaS applications where users have different roles in different organizations/tenants.

User Type with Tenant Roles

import type { TenantUser } from '@alfredaoo/auth-policies'

// Define system-wide and tenant-specific roles
type SystemRole = 'SUPER_ADMIN' | 'USER'
type TenantRole = 'OWNER' | 'ADMIN' | 'MEMBER'

interface User extends TenantUser<SystemRole, TenantRole> {
  id: string
  email: string
  role: SystemRole                                    // System-wide role
  tenantRoles?: Record<string, TenantRole>            // Per-tenant roles
}

// Example user with different roles in different businesses
const user: User = {
  id: '1',
  email: '[email protected]',
  role: 'USER',
  tenantRoles: {
    'business-1': 'OWNER',    // Owner of business-1
    'business-2': 'MEMBER',   // Member of business-2
  },
}

Configure Multi-Tenancy

const auth = createAuth<User, SystemRole, 'Appointment'>({
  // System-level role permissions (apply globally)
  rolePermissions: {
    SUPER_ADMIN: ['*'],
    USER: ['view.profile'],
  },
  policies: {
    Appointment: AppointmentPolicy,
  },
  getUser: async () => getCurrentUser(),

  // Tenant configuration
  tenant: {
    // Tenant-level role permissions
    rolePermissions: {
      OWNER: ['manage.business', 'view.reports', 'manage.staff'],
      ADMIN: ['view.reports', 'manage.staff'],
      MEMBER: ['view.appointments'],
    },
    // How to get the current tenant context (e.g., from active business)
    getTenantId: async () => {
      const session = await getSession()
      return session?.activeBusinessId ?? null
    },
  },
})

Tenant-Aware Policies

import { getTenantRoles } from '@alfredaoo/auth-policies'

const AppointmentPolicy = {
  view: (user, resource, tenantId) => {
    // SUPER_ADMIN can view all
    if (user.role === 'SUPER_ADMIN') return true

    // Check tenant role
    if (!tenantId) return false
    const tenantRoles = getTenantRoles(user, tenantId)
    return tenantRoles.length > 0
  },

  manage: (user, resource, tenantId) => {
    if (user.role === 'SUPER_ADMIN') return true
    if (!tenantId) return false

    const tenantRoles = getTenantRoles(user, tenantId)
    return tenantRoles.includes('OWNER') || tenantRoles.includes('ADMIN')
  },
}

Using Tenant Context

// Uses tenant from config (getTenantId)
await auth.canApi('view', 'Appointment')

// Override tenant for specific check
await auth.canApi('view', 'Appointment', { tenantId: 'business-2' })

// In middleware, extract tenant from request
export const PUT = withAuth(handler, {
  action: 'manage',
  type: 'Appointment',
  getTenantId: (req) => {
    const url = new URL(req.url)
    return url.searchParams.get('businessId')
  },
})

Tenant Permission Checker

import { createTenantPermissionChecker } from '@alfredaoo/auth-policies'

const checker = createTenantPermissionChecker({
  systemRolePermissions: {
    SUPER_ADMIN: ['*'],
    USER: ['view.profile'],
  },
  tenantRolePermissions: {
    OWNER: ['manage.business', 'view.reports'],
    MEMBER: ['view.appointments'],
  },
})

// Check combined permissions (system + tenant)
checker.hasPermission(user, 'manage.business', 'business-1')  // true (OWNER)
checker.hasPermission(user, 'manage.business', 'business-2')  // false (MEMBER)
checker.hasPermission(user, 'view.profile', null)             // true (USER)

// Get all permissions in a tenant context
checker.getPermissions(user, 'business-1')
// ['view.profile', 'manage.business', 'view.reports']

// Get roles breakdown
checker.getRoles(user, 'business-1')
// { system: ['USER'], tenant: ['OWNER'] }

Tenant Utilities on Auth Instance

// Access tenant utilities
auth.tenant?.getTenantRoles(user, 'business-1')    // ['OWNER']
auth.tenant?.hasPermission(user, 'manage.business', 'business-1')
auth.tenant?.getPermissions(user, 'business-1')

// Check if multi-tenancy is enabled
auth.config.tenantEnabled  // true

Cache Invalidation by Tenant

// Invalidate cache for a specific tenant (e.g., after role changes)
auth.cache?.invalidateTenant('business-1')

Audit Logging with Tenant

const auth = createAuth({
  // ...
  onAudit: (entry) => {
    console.log('Tenant:', entry.tenantId)  // The tenant context used
  },
})

TypeScript

The library is written in TypeScript and provides full type safety:

import type { BaseUser, Policy, PolicyMap, AuthConfig } from '@alfredaoo/auth-policies'

// Your user type must extend BaseUser
interface MyUser extends BaseUser<'ADMIN' | 'USER'> {
  id: string
  role: 'ADMIN' | 'USER'
  email: string
}

// Policies are type-safe
const MyPolicy: Policy<MyUser> = {
  view: async (user, resource) => true,
  update: async (user, resource) => user.role === 'ADMIN',
}

License

MIT