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

@flusys/nestjs-iam

v9.2.1

Published

Identity and Access Management (IAM) module for NestJS applications

Downloads

2,598

Readme

@flusys/nestjs-iam

Identity and Access Management for NestJS — RBAC, DIRECT, and FULL permission modes with caching and multi-tenant support.

npm version License: MIT


Installation

npm install @flusys/nestjs-iam @flusys/nestjs-shared @flusys/nestjs-core

1. Register the Module

Synchronous

Mode 1: Single Database

import { IAMModule } from '@flusys/nestjs-iam';

@Module({
  imports: [
    IAMModule.forRoot({
      global: true,
      includeController: true,
      bootstrapAppConfig: {
        databaseMode: 'single',
        enableCompanyFeature: false,
        permissionMode: 'RBAC', // 'RBAC' | 'DIRECT' | 'FULL' (default: 'FULL')
      },
      config: {
        defaultDatabaseConfig: {
          type: 'mysql',
          host: process.env.DB_HOST,
          port: Number(process.env.DB_PORT ?? 3306),
          username: process.env.DB_USER,
          password: process.env.DB_PASSWORD,
          database: process.env.DB_NAME,
        },
      },
    }),
  ],
})
export class AppModule {}

Mode 2: Multi-Tenant

IAMModule.forRoot({
  global: true,
  includeController: true,
  bootstrapAppConfig: {
    databaseMode: 'multi-tenant',
    enableCompanyFeature: true,
    permissionMode: 'FULL',
  },
  config: {
    tenantDefaultDatabaseConfig: {
      type: 'mysql',
      host: process.env.TENANT_DB_HOST,
      port: Number(process.env.TENANT_DB_PORT ?? 3306),
      username: process.env.TENANT_DB_USER,
      password: process.env.TENANT_DB_PASSWORD,
      database: process.env.TENANT_DB_NAME,
    },
    tenants: [
      { id: 'tenant-a', database: 'tenant_a_db', permissionMode: 'FULL' },
      { id: 'tenant-b', database: 'tenant_b_db', permissionMode: 'RBAC' },
    ],
  },
});

Asynchronous (with ConfigService)

import { ConfigModule, ConfigService } from '@nestjs/config';
import { IAMModule, ITenantDatabaseConfig } from '@flusys/nestjs-iam';

// Single database
IAMModule.forRootAsync({
  global: true,
  includeController: true,
  bootstrapAppConfig: {
    databaseMode: 'single',
    enableCompanyFeature: true,
    permissionMode: 'FULL',
  },
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    defaultDatabaseConfig: {
      type: 'mysql',
      host: configService.get('DB_HOST'),
      port: configService.get<number>('DB_PORT'),
      username: configService.get('DB_USER'),
      password: configService.get('DB_PASSWORD'),
      database: configService.get('DB_NAME'),
    },
  }),
  inject: [ConfigService],
});

// Multi-tenant
IAMModule.forRootAsync({
  global: true,
  includeController: true,
  bootstrapAppConfig: {
    databaseMode: 'multi-tenant',
    enableCompanyFeature: true,
    permissionMode: 'FULL',
  },
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    tenantDefaultDatabaseConfig: {
      type: 'mysql',
      host: configService.get('TENANT_DB_HOST'),
      port: configService.get<number>('TENANT_DB_PORT'),
      username: configService.get('TENANT_DB_USER'),
      password: configService.get('TENANT_DB_PASSWORD'),
      database: configService.get('TENANT_DB_NAME'),
    },
    tenants: configService.get<ITenantDatabaseConfig[]>('TENANTS'),
  }),
  inject: [ConfigService],
});

2. Register Entities in TypeORM

Use getIAMEntitiesByConfig() — arguments must match bootstrapAppConfig:

import { getIAMEntitiesByConfig } from '@flusys/nestjs-iam/entities';

TypeOrmModule.forRoot({
  entities: [
    ...getIAMEntitiesByConfig(
      true, // enableCompanyFeature
      'FULL', // permissionMode: 'FULL' | 'RBAC' | 'DIRECT'
    ),
  ],
});

3. Protect Endpoints

All FLUSYS endpoints use POST. Apply JwtAuthGuard and @RequirePermission from nestjs-shared:

import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
import { RequirePermission, CurrentUser } from '@flusys/nestjs-shared/decorators';
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';

@UseGuards(JwtAuthGuard)
@Controller('products')
export class ProductController {
  @Post('insert')
  @RequirePermission('product.create')
  async create(@CurrentUser() user: ILoggedUserInfo) {
    /* ... */
  }

  @Post('get-all')
  @RequirePermission('product.read')
  async getAll(@CurrentUser() user: ILoggedUserInfo) {
    /* ... */
  }
}

Wildcard matching is supported: @RequirePermission('product.*') matches any action whose code starts with product..

4. Programmatic Permission Check

Inject PermissionService (use @Inject() — required for bundled code):

import { PermissionService } from '@flusys/nestjs-iam';

@Injectable()
export class ProductService {
  constructor(@Inject(PermissionService) private readonly permissionService: PermissionService) {}

  async canCreate(userId: string, companyId?: string, branchId?: string): Promise<boolean> {
    return this.permissionService.hasPermission(userId, 'product.create', companyId, branchId);
  }
}

hasPermission checks the user's backend codes with matchesPermission() from @flusys/nestjs-shared/utils (* and prefix.* wildcards).

For other packages: PERMISSION_RESOLVER

IAMModule provides and exports the PERMISSION_RESOLVER provider interface from @flusys/nestjs-shared (PermissionResolverAdapter). getPermissionCodes({ userId, companyId?, branchId? }) returns the user's backend codes for any branch - cached, or rebuilt from roles and direct grants and cached (PermissionCacheService.getBackendCodes). Feature packages inject it @Optional() (nestjs-entity-builder uses it for HAS_PERMISSION(...) rules) and never import nestjs-iam.

PERMISSION_ACTION_REGISTRY (PermissionActionRegistryAdapter) lets a feature package create, revive and remove Actions at runtime. Both adapters are singletons that resolve the request-scoped services per call (iam.module.scope.spec.ts guards this).

5. Permission Resolution Rules

  • RBAC: user roles -> role actions. DIRECT: direct user actions. FULL: both, merged.
  • Only active, non-deleted actions count, only active, non-deleted roles count, and grants outside validFrom / validUntil are ignored.
  • Company feature on: a session sees its company's company-wide grants plus its branch's (all of its branches when it has no branch); a session without a company sees only company-less grants, never every company's. A role only grants inside its own company.
  • The permission cache (one version stamp per user, see nestjs-shared README section 7) is dropped after every change that affects it, always after the write commits, for every user it reaches - computed before any grant row is deleted: assignments, role update / delete, action update / delete (a permanent delete also covers the child actions the parent FK cascades to), company whitelist removals (the whitelist itself only limits what may be assigned; removing an action from it deletes the company's role and direct grants of it), company / branch / user access revocation, and actions registered or removed through PERMISSION_ACTION_REGISTRY.
  • A cached permission set expires no later than the next validFrom / validUntil boundary of the grants it was built from, so a time-bound grant starts and stops counting on time.
  • Actions and roles written outside ActionService / RoleService (the action registry, company revocation) bump that entity's ApiService cache themselves.

6. Assignment Rules

The assignment endpoints pass the caller to PermissionService (assignUserActions(dto, actor), assignUserRoles(dto, actor), assignRoleActions(dto, actor)); a call without an actor is a trusted system call.

  • Company feature on: the grant's company must be the caller's own (it defaults to it when omitted), roles must belong to that company, actions must be on that company's whitelist (company-actions/assign), and - when nestjs-auth's COMPANY_ACCESS_RESOLVER is available - the target user (and branch) must belong to that company. Reads (get-user-actions, get-user-roles, get-role-users, get-action-users, get-role-actions, actions/tree-for-permission) are limited to the caller's company the same way.
  • No self-escalation: nobody can grant themselves an action, or a role carrying an action, they do not already hold, nor add an action to a role they hold (403 error.insufficient.permissions).
  • Cross-company assignment (onboarding a new company): user-actions/assign, user-roles/assign, get-user-actions and get-user-roles accept another companyId only when the caller holds cross-company.assign (CROSS_COMPANY_PERMISSIONS.ASSIGN) in their own session scope - an explicit grant, never matched by * or cross-company.*. Every other check still runs against the target company (its roles, its whitelist, target user membership), and every action granted there - directly or through a role, to anyone - must be one the caller holds. Without the grant the call is refused with 403 auth.company.no.access. The seed gives it to the admin; grant it only to platform/onboarding admins.
  • Unknown or deleted actions / roles are refused (404); a concurrent duplicate assignment is a 409 permission.already.exists on PostgreSQL, MySQL, SQLite and SQL Server.
  • Roles: company users only create, read, update and delete roles of their own company.

Exported Services

| Service | Scope | Description | | ------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------- | | PermissionService | REQUEST | hasPermission(), getMyPermissions(), assign*(), getUserRoles(), getUserActions(), getUserEffectiveRoles(), getUserEffectiveActions(), revoke*() | | PermissionCacheService | REQUEST | getBackendCodes(), getHeldActions(), invalidateUser(s)(), invalidateRoleMembersCache(), findActionHolderIds(), findRoleMemberIds(), findCompanyMemberIds() | | RoleService | REQUEST | Role CRUD (RBAC / FULL only), company scoped | | ActionService | REQUEST | Action CRUD, getActionTree(), getActionsForPermission() |

All constructor injections need explicit @Inject() — TypeScript metadata is lost during esbuild bundling.


License

MIT © FLUSYS