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

nest-access-policy

v0.1.0

Published

Declarative and centralized access control.

Readme

Nest Access Policy

Declarative and centralized access control.

Inspired by drf-access-policy.

Tutorial

Creating the Access Policy

An AccessPolicy is a special provider where we define our access statements.

A common usage is to make access policies and controllers have one-to-one relationships.

// books.access-policy.ts

@Injetable()
export class BooksAccessPolicy implements AccessPolicy {
  statements: AccessPolicyStatement[] = [
    {
      actions: ["list", "create", "retrieve", "update", "destroy"],
      effect: Effect.Allow,
    },
  ];
}

Each method of the controller is considered as an action. The actions defines the names of the actions controlled by the statement.

There are two kinds of statements: allow statements with effect set to Effect.Allow and forbid statements with effect set to Effect.Forbid. An action is allowed only when all allow statements are passed (at least one) and no forbid statements are passed.

Therefore, it is recommended to allow all the actions in the first statement as above and manage the permissions in next statements.

The AccessPolicy, AccessPolicyStatement, AccessPolicyCondition types takes two optional type args, the first is the actions' type while the second is the request's type.


Now let's define the really important parts:

// books.access-policy.ts

@Injetable()
export class BooksAccessPolicy implements AccessPolicy {
  private isOwn: AccessPolicyCondition = async ({ req }) =>
    (await this.getBook(req)).owner == req.user;

  private notOwn: AccessPolicyCondition = async (ctx) =>
    !(await this.isOwn(ctx));

  private isPublic: AccessPolicyCondition = async ({ req }) =>
    (await this.getBook(req)).isPublic;

  private notImmutable: AccessPolicyCondition = async ({ req }) =>
    !(await this.getBook(req)).isImmutable;

  statements: AccessPolicyStatement[] = [
    {
      actions: ["list", "create", "retrieve", "update", "destroy"],
      effect: Effect.Allow,
    },
    {
      actions: ["retrieve"],
      effect: Effect.Forbid,
      conditions: [this.notOwn],
    },
    {
      actions: ["update", "destroy"],
      effect: Effect.Allow,
      conditions: [this.notImmutable, [this.isOwn, this.isPublic]],
    },
  ];

  @Inject()
  private booksService: BooksService;

  private async getBook(req: Request) {
    if (!req.entity) {
      const id = Number(req.params.id);
      const book = await this.booksService.retrieve(id);
      if (!book) throw new NotFoundException();
      req.entity = book;
    }
    return req.entity;
  }
}

All the elements in conditions are considered a condition group, which can be either a single condition or a list of conditions.
The logical relationship between the condition groups is and, and the one between the member conditions of each condition groups is or.

Since an AccessPolicy is a class, we can define the common conditions in a public policy such as BaseAccessPolicy and inherit the common conditions from it.

If you prefer to define the conditions after the statements, you can make the statements a getter.


Although the statements above work well, they lack of error messages. Actually they are also organized improperly: The failure of a statement should be able to be described as a single reason.

Let's fix this:

// books.access-policy.ts

@Injetable()
export class BooksAccessPolicy implements AccessPolicy {
  // ...
  statements: AccessPolicyStatement[] = [
    // ...
    {
      actions: ["retrieve", "update", "destroy"],
      effect: Effect.Allow,
      conditions: [[this.isOwn, this.isPublic]],
      reason: "Only your own books or public books can be managed",
    },
    {
      actions: ["update", "destroy"],
      effect: Effect.Allow,
      conditions: [this.notImmutable],
      reason: "The immutable books cannot be managed",
    },
  ];
  // ...
}

When checking the statements, if a statement causes the request to be denied, such as a failed allow statement or a passed forbid statement, an ForbiddenException will be thrown with the reason of the statement as its message.

Applying the Access Policy

Now we've done with the AccessPolicy, it only takes a few steps to apply it.

Since the AccessPolicy is injectable, it should be added to the providers list of the module. Whatsmore, we will use a AccessPolicyGuard to protect our routes, who injects AccessPolicyService to check the statements, so we also need to import the AccessPolicyModule:

// books.module.ts

@Module({
  // ...
  imports: [
    // ...
    AccessPolicyModule,
    // ...
  ],
  providers: [
    // ...
    BooksAccessPolicy,
    // ...
  ],
  // ...
})
export class BooksModule {}

Finally, of course, use the guard and apply the policy:

// books.controller.ts

@UseAccessPolicies(BooksAccessPolicy)
@UseGuards(AccessPolicyGuard)
@Controller()
export class BooksController {}

To access req.user, you should ensure the AuthGuard is before the AccessPolicyGuard. See the request lifecycle for more help.