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

pundit-ts

v0.8.0

Published

Authorization library in pure typescript.

Readme

Pundit-TS - Plain Typescript Authorization Library

Pundit-TS is an authorization library highly-inspired by the pundit gem.

https://github.com/user-attachments/assets/c7815622-c1c9-4fbe-986e-6c9f88c8b31d

Use Cases

  • Apply RBAC, ABAC, DAC models (see the example snippets below for how to implement)
  • Check if a user is authorized to perform an action on an entity (ie. Post, Product, Category etc..)
  • Declare actions can be performed per entity class basis
    • UserActions: create, update
    • PostActions: create, publish, unpublish, update, delete
    • CategoryActions: create, update, delete
  • Filter entities on database to avoid unnecessary database queries
    • Apply joins, specific where clauses or similar things to filter database rows.
  • Apply user tiers: only premium users can add more than 3 seats to their organization.
  • Avoid code duplication: Keep your authorization logic in one place. Use whenever you need them. Change one place to affect rest of the code.

Examples

Blog: Plain typescript blog example. Has no relation with a database. Great starting point if you are just starting to use pundit-ts

Prisma Blog: Prisma ORM based blog example. Advanced version of the plain blog example. Uses Prisma ORM for querying database, utilizes PostPolicy#filter for building argument for prisma.post.findMany method.

Installation

npm i -S pundit-ts

🔒 Access Control Models

Here some examples to utilize pundit-ts for applying common access control models like RBAC, ABAC etc..

class Policy {
  authorize(ctx, object, action) {
    const isAuthenticated = ctx.actor !== null;
    const role = ctx.actor?.role;
    const isAdmin = role === "admin";
    const isEditor = role === "editor";

    switch (action) {
      case "create":
        return isAdmin || isEditor;
      case "delete":
        return isAdmin;
      case "view":
        return true; // everyone, including anonymous users can view everything
      default:
        return false; // disallow every other action
    }
  }
}
class Policy {
  authorize(ctx, object, action) {
    // non logged-in users cannot perform any action...
    if (ctx.actor === null) {
      throw new UnauthorizedError();
    }

    const role = ctx.actor.role;
    const isAdmin = role === "admin";
    const isEditor = role === "editor";

    switch (action) {
      case "delete": // only admins can delete the record
        return isAdmin;

      // update permissions
      case "update:content":
        return isAdmin || isEditor;
      case "update:title":
        return isAdmin;

      case "view:content":
      case "view:title":
        return true; // everyone can view title and content

      default:
        return false;
    }
  }
}

Great choice for multi-tenant applications (ie. SaaS applications). Here we use organization as our tenant.

class DocumentPolicy {
  authorize(ctx, object, action) {
    if (ctx.actor === null) {
      return false; // 1. anonymous users cannot perform anything
    }

    const isOrganizationOwner = ctx.actor.id === object.organization.owner_id;

    if (isOrganizationOwner) {
      return true; // 2. organization owner can perform any action
    }

    // 3. check if the actor is member of the related organization
    const member = object.organization.members.findById(ctx.actor.id);

    if (!member) {
      return false;
    }

    switch (action) {
      case "create":
        return member.permissions.canCreateDocument;
      case "update":
        return member.permissions.canUpdateDocument;

      // 4. Even combine with the Attribute-Based Access Control (ABAC) model
      case "update:title":
        return member.permissions.canUpdateDocumentTitle;

      default:
        return false;
    }
  }
}

🔑 Authorize users

Encapsulate your authorization logic behind your PunditPolicy implementations. Reuse those policies when you need. Manage your authorization logic from one place.

const currentUser = {}; // get user from cookies, headers, jwt etc...

// update post

// fetch post from db
const post = await prisma.post.findFirst({ where: { id: 123 } });

- if (post.authorId === currentUser.id) {
-   // update logic...
- }
+ if (await pundit.authorize(currentUser, post, 'update')) {
+   // update logic...
+ }

🔎 Filter entitites

Pundit-TS is a ORM-agnostic library. You may use your choice of ORM, query builder or anything.

-prisma.post.findMany({ /* your arguments  */ })
+prisma.post.findMany(pundit.filter(context, Post))

⌨️ Usage

Declare your models.

// models.ts
class User {}

class Post {}

Declare your actions for each model.

// actions.ts
export type UserActions = "create" | "delete" | "update";
export type PostActions = "create" | "delete" | "update";

Declare your policies

// policies.ts
import { PunditPolicy } from "pundit-ts";
import { Post, User } from "./models";
import { PostActions, UserActions } from "./actions";

export class PolicyContext {
  // your orm related properties might go here
}

export class PostPolicy extends PunditPolicy<PolicyContext, Post, PostActions> {
  constructor() {
    super(Post);
  }

  authorize(context, post, action) {
    switch (action) {
      case "create":
        return true;
      case "delete":
        return false; // to be implemented...
      case "update":
        return false; // to be implemented...
    }
  }

  filter(ctx) {
    // modify context
  }
}

export class UserPolicy extends PunditPolicy<PolicyContext, User, UserActions> {
  constructor() {
    super(User);
  }

  authorize(context, post, action) {
    switch (action) {
      case "create":
        return true;
      case "delete":
        return false; // to be implemented...
      case "update":
        return false; // to be implemented...
    }
  }

  filter(ctx) {
    // modify context or return some filter object...
  }
}

Create your Pundit instance:

// pundit.ts
import { Pundit } from "pundit-ts";
import { PostPolicy, UserPolicy } from "./policies";

export const pundit = new Pundit<PolicyContext>()
  .register(new UserPolicy())
  .register(new PostPolicy());

Authorize your actions in a fully type-safe way.

// index.ts
import { PolicyContext } from "./policies";
import { Post } from "./models";
import { pundit } from "./pundit";

const ctx = new PolicyContext();
const post = new Post();

await pundit.authorize(ctx, post, "create");