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 🙏

© 2025 – Pkg Stats / Ryan Hefner

authz-gate

v1.2.0

Published

A TypeScript-first authorization library that provides a flexible, extensible, and strongly typed system for defining and enforcing access control. Supports both Gates (global abilities) and Policies (resource-specific logic) with full type inference and

Readme

authz-gate

A Laravel-inspired, framework-agnostic authorization system for Node.js, offering type-safe Gates & Policies for granular access control.

NPM Version TypeScript License: MIT

A TypeScript-first authorization library that provides a flexible, extensible, and strongly typed system for defining and enforcing access control. Supports both Gates (global abilities) and Policies (resource-specific logic) with full type inference and modern async/await support.

✨ Features

  • 🎯 Type-Safe Authorization: Full TypeScript support with parameter inference and autocompletion
  • 🔗 Fluent Builder API: Clean, readable ability definitions via initialize() function
  • 📓 Declarative API: Define rules once, enforce everywhere with consistent behavior
  • 🏗️ Policy System: Resource-specific authorization logic with automatic method resolution
  • Async Support: Both synchronous and asynchronous ability callbacks
  • 🔄 Lifecycle Hooks: before() and after() hooks for global and policy-level authorization
  • 🧩 Framework Agnostic: Core logic independent of any HTTP framework
  • 📦 Zero Dependencies: No external dependencies in core library
  • 🔍 Strict Validation: Compile-time ability name validation and runtime checks

📦 Installation

npm install authz-gate
# or
yarn add authz-gate
# or
pnpm add authz-gate
// ES Modules
import { initialize, Policy } from 'authz-gate';

// CommonJS
const { initialize, Policy } = require('authz-gate');

🚀 Quick Start

Simple Authorization

import { initialize } from 'authz-gate';

// Define your abilities
const gate = initialize(keeper =>
  keeper
    .define('view-posts', (user: User) => user.hasPermission('read'))
    .define('create-post', (user: User) => user.role === 'editor')
    .define('edit-post', (user: User, post: Post) => user.id === post.authorId)
    .define('delete-post', (user: User, post: Post, isPublished: boolean) => user.id === post.authorId && !isPublished)
    .define('publish-post', async (user: User) => await checkUserHasAdminPermission(user.id))
);

// Use anywhere in your app
const canEdit = await gate.allows('edit-post', user, post);
const cannotDelete = await gate.denies('delete-post', user, post, true);

if (canEdit) {
  // User is authorized to edit the post
}

Best Practice: Define your gate in a separate module (e.g., ./auth/gate.ts) and export it for consistent use across your application.

🔧 Core API

Basic Permission Checks

// Single permission check
const allowed = await gate.allows('edit-post', user, post);
const denied = await gate.denies('edit-post', user, post);

Batch Permission Checks

Check multiple permissions at once:

// All permissions must pass
const hasAllPermissions = await gate.all({
  'view-posts': [user],
  'edit-post': [user, post]
});

// Any permission can pass
const hasAnyPermission = await gate.any({
  'delete-post': [user, post, false],
  'moderate-content': [user]
});

// No permissions should pass
const hasNoAdminRights = await gate.none({
  'delete-post': [user, post, false],
  'admin-panel': [user]
});

// Alternative tuple syntax
const canPerformActions = await gate.allOf([
  ['view-posts', user],
  ['edit-post', user, post]
]);

const canModerate = await gate.anyOf([
  ['delete-post', user, post],
  ['moderate-content', user]
]);

const isNotAdmin = await gate.noneOf([
  ['admin-panel', user],
  ['delete-user', user, otherUser]
]);

🏗️ Policy System

For resource-specific authorization logic, use Policy classes. Policies automatically map to abilities using the resource:method pattern:

import { initialize, Policy } from 'authz-gate';

// Define a Policy class for Message resource
class MessagePolicy extends Policy {
  // Optional: Policy-level before hook
  before(ability: string, user: User, message?: Message) {
    // Message authors can perform any action on their messages
    if (message && user.id === message.authorId) return true;

    // Continue to specific method
    return null;
  }

  view(user: User, message: Message, isPublic?: boolean): boolean {
    // Public messages or same organization
    return isPublic || user.organizationId === message.organizationId;
  }

  delete(user: User): boolean {
    // Only admins can delete (author check handled by before hook)
    return user.role === 'admin';
  }
}

// Register the policy
const gate = initialize(keeper =>
  keeper
    .policy('message', new MessagePolicy())

    // Still can define global abilities
    .define('create-message', (user: User) => user.isVerified)
);

// Usage - calls MessagePolicy methods automatically
const canView = await gate.allows('message:view', user, message);
const canDelete = await gate.allows('message:delete', user, message);
const canCreate = await gate.allows('create-message', user);

🔥 Advanced Examples

Role-Based Access Control (RBAC)

interface User {
  id: string;
  role: 'admin' | 'moderator' | 'user';
  permissions: string[];
}

const gate = initialize(keeper =>
   keeper
    .before((ability, user) => {
      // Admins can do everything
      if (user.role === 'admin') return true;
      return null;
    })

    .define('moderate-content', (user: User) =>
      ['admin', 'moderator'].includes(user.role)
    )

    .define('view-admin-panel', (user: User) => user.role === 'admin')
);

Attribute-Based Access Control (ABAC)

const gate = initialize(keeper =>
  keeper
    .before((ability, user, resource) => {
      // Tenant isolation
      if (resource?.tenantId && user.tenantId !== resource.tenantId) {
        return false;
      }
      return null;
    })

    .define('view-document', (user, document) => {
      return document.published && user.hasPermission('documents.view');
    })

    .after((result, ability, user, resource) => {
      // Audit logging
      if (resource?.id) {
        auditLog.record({
          tenantId: user.tenantId,
          userId: user.id,
          action: ability,
          resourceId: resource.id,
          granted: result
        });
      }
      return result;
    })
);

Time-Based Access Control (TBAC)

const gate = initialize(keeper =>
  keeper
    .before((ability, user) => {
      const hour = new Date().getHours();

      // Maintenance window - admins only
      if (hour >= 2 && hour <= 4 && user.role !== 'admin') {
        return false;
      }

      // Business hours for financial operations
      if (ability.includes('financial') && (hour < 9 || hour > 17)) {
        return user.hasPermission('after-hours-access');
      }

      return null;
    })
);

🛠️ Error Handling

Errors are properly propagated through the authorization chain:

const gate = initialize(keeper =>
  keeper
    .before(async (ability, user) => {
      // If this throws, the error propagates
      if (await validateUserSession(user.sessionId)) {
        return true;
      }
      return null;
    })
    .define('secure-action', (user) => true)
);

try {
  await gate.allows('secure-action', user);
} catch (error) {
  // Handle session validation errors
}

Errors propagate immediately and stop the authorization chain. Use try-catch blocks around authorization check calls when callbacks perform async operations that might throw.

🎓 Core Concepts

Think of Gates as global authorization rules and Policies as resource-specific authorization rules. Use Gates for application-wide permissions (user roles, feature flags). Use Policies for resource-specific logic (post ownership, document access).

Gate and Keeper

  • initialize(initializer) creates a new keeper internally, passes it into your initializer so you can register hooks/abilities/policies, and returns a gate instance.
  • Do all registrations inside the initializer chain; initialize returns only the gate instance.

Abilities

  • Abilities are exact string keys such as "create:post", "post_view", "post-edit", etc.
  • Define with keeper.define("ability", (...args) => boolean | Promise<boolean>).
  • If an ability is not defined and no matching policy exists, the result defaults to false.

Policies

  • Register with keeper.policy("resource", PolicyClass). Abilities shaped as "resource:method" are routed to matching methods on the policy class.
  • Precedence: A registered policy takes precedence for "resource:method" and "resource" abilities over a .define(...) with the same name.

Hooks

  • before(...args): run before the main check (global + optional policy hook).
    • Return trueallow immediately.
    • Return falsedeny immediately.
    • Return null | undefined → continue to the main check.
  • after(result, ability?, ...args): run after the main check (global + optional policy hook).
    • Usually cosidered as a logging/cleanup hook, but can be used to modify the result if the result of the main check is nullish.

Execution Order

  1. Global Before Hooks → Execute first for all abilities
  2. Policy Before Hook → If routed ability matches a policy, run for policy methods
  3. Policy Method → If routed ability matches a policy, run the specific authorization logic for the resource (e.g., MessagePolicy.view())
  4. Ability Method → If policy method returns null or undefined, run the ability method
  5. Global After Hooks → Execute last for logging/cleanup for all main checks

🔄 Lifecycle Hooks

Lifecycle hooks provide powerful global control over authorization flow:

Before Hooks

before() hooks run before any specific ability check. They can:

  • Grant access by returning true (skips ability check afterwards)
  • Deny access by returning false (skips ability check afterwards)
  • Continue by returning null or undefined (proceeds to ability check)
const gate = initialize(keeper =>
  keeper
    .before((ability, user) => {
      // Global admin access
      if (user.role === 'super-admin') return true;

      // Reject all access if the user is suspended
      if (user.isSuspended) return false;

      // Continue to specific ability
      return null;
    })

    .before(async (ability, user, resource) => {
      if (ability.startsWith('admin-')) {
        const hasAdminAccess = await checkAdminPermissions(user.id);
        return hasAdminAccess;
      }
    })

    .define('edit-post', (user, post) => user.id === post.authorId)
);

After Hooks

after() hooks run after the ability check and can modify the final result:

const gate = initialize(keeper =>
  keeper
    .define('view-sensitive-data', (user) => user.clearanceLevel >= 5)

    .after((result, ability, user) => {
      // Log all authorization decisions
      logger.log({
        userId: user.id,
        ability,
        granted: result,
        timestamp: new Date()
      });

      return result; // Return unchanged
    })

    .after((result, ability, user) => {
      // Fallback for nullish result
      if ((result === null || result === undefined) && user.role === 'admin') return true;
    })
);

Multiple before/after hooks are supported and execute in registration order. However, organizing all global authorization logic in single hooks is recommended for maintainability.

NOTE: If any before hook returns a non-nullish value (true or false), execution stops there and subsequent before hooks and ability checking are skipped. Only the available after hooks will run after that.

🎯 Ability Naming Conventions

The library supports multiple naming conventions for abilities:

// ✅ Supported formats
"create-post"         // kebab-case
"create_post"         // snake_case
"create:post"         // colon:separator (used for policies)
"create|post"         // pipe|separator
"create.post"         // dot.notation
"create post"         // space separator
"createPost"          // camelCase
"CreatePost"          // PascalCase
"create"              // any alphabetic string

// 🎯 Policy method resolution
"message:view"        // Calls MessagePolicy.view()
"message:update"      // Calls MessagePolicy.update()
"message:delete"      // Calls MessagePolicy.delete()

// 🔮 Wildcard support (planned)
"*:post"              // Any action on post resource
"message:*"           // Any action on message resource
"*"                   // Any action on any resource

📚 API Reference

initialize(initializer)

function initialize<T>(
  initializer: (keeper: GateKeeperContract) => GateKeeperContract<T>
): Gate<T>

Returns a configured Gate. All registrations must be done inside initializer.


GateKeeperContract Methods

// Lifecycle hooks
keeper.before(callback: BeforeCallback): GateKeeperContract
keeper.after(callback: AfterCallback): GateKeeperContract

// Ability definition
keeper.define(ability: string, callback: AbilityCallback): GateKeeperContract

// Policy registration
keeper.policy(resource: string, instance: Policy): GateKeeperContract

Gate Methods

// Single checks
gate.allows(ability: string, ...args: unknown[]): Promise<boolean>
gate.denies(ability: string, ...args: unknown[]): Promise<boolean>

// Batch checks - Object mapper
gate.all(mapper: AbilityMapper): Promise<boolean>
gate.any(mapper: AbilityMapper): Promise<boolean>
gate.none(mapper: AbilityMapper): Promise<boolean>

// Batch checks - Tuple arrays
gate.allOf(tuples: AbilityTuple[]): Promise<boolean>
gate.anyOf(tuples: AbilityTuple[]): Promise<boolean>
gate.noneOf(tuples: AbilityTuple[]): Promise<boolean>

Callback Types

// Ability definition
type AbilityCallback = (...args: readonly unknown[]) => boolean | null | undefined | Promise<boolean | null | undefined>

// Lifecycle hooks
type BeforeCallback = (ability: string, ...args: readonly unknown[]) => boolean | null | undefined | Promise<boolean | null | undefined>
type AfterCallback = (result: boolean | null | undefined, ability: string, ...args: readonly unknown[]) => boolean | null | undefined | Promise<boolean | null | undefined>

Policy base class

abstract class Policy {
  before?(ability?: string, ...args: unknown[]): boolean | null | Promise<boolean | null>

  // Your methods here will be auto-mapped to abilities
  // e.g., view() → "resource:view"
}

🗺️ Roadmap

The following features are planned for future releases:

  • Policy Auto-Discovery - Automatic policy registration via directory scanning
  • User Resolution - Automatic user injection from request context
  • Framework Adapters - Express, Fastify, and Nest.js middleware integration
  • Nest.js Decorators - @Can(), @Cannot() route decorators
  • Wildcard Support - message:*, *:post, * ability patterns

📁 Project Structure

src/
├── core/
│   ├── ability-evaluator.ts                    # Ability execution engine
│   ├── gate-keeper.ts                          # Ability definition and registration
│   ├── gate.ts                                 # Authorization checking logic
│   ├── policy.ts                               # Abstract Policy base class
│   └── index.ts                                # Core exports
├── interfaces/
│   ├── ability-evaluator-config.interface.ts   # Evaluator configuration
│   ├── gate-keeper.interface.ts                # GateKeeper contract
│   ├── policy-ability-parts.interface.ts       # Policy parsing interface
│   └── index.ts                                # Interface exports
├── types/
│   ├── ability-registry.type.ts                # Registry and argument types
│   ├── ability.type.ts                         # Type-safe ability validation
│   ├── auth-response.type.ts                   # Response type definitions
│   ├── callback.type.ts                        # Callback function types
│   ├── policy.type.ts                          # Policy method type utilities
│   └── index.ts                                # Type exports
├── utils/
│   ├── array.utils.ts                          # Array utility functions
│   ├── object.utils.ts                         # Object utility functions
│   ├── type.utils.ts                           # Type checking utilities
│   └── index.ts                                # Utility exports
└── index.ts                                    # Main entry point

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.