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

@elegantys/polisafe

v0.1.0

Published

SDK NestJS para integrar @Permission/@Scope y autenticacion JWT/OpenID contra un servidor Polisafe (Polizei).

Readme

@elegantys/polisafe

NestJS client SDK for Polisafe IAM — protect your REST APIs with the authentication and authorization power of Polisafe, the multi-tenant Identity & Access Management platform (formerly Polizei).

Drop-in decorators (@Permission, @Scope, @UserToken, …), guards and Passport strategies that validate the RS256 JWTs issued by a Polisafe IAM server and resolve authorization — locally from token claims or remotely against the IAM server — so your services never re-implement auth.


Table of contents


What it does

  • Authenticates every request by validating the Authorization: Bearer JWT (or a session cookie) against the Polisafe IAM server's per-tenant JWKS (RS256).
  • Resolves the tenant of the request from the token and the optional X-Tenant header.
  • Authorizes the route: the permission is checked either remotely (the IAM server decides, POST …/polizei/io/authorization) or locally against the permissions claims embedded in the access token.
  • Speaks machine-to-machine: it obtains and automatically renews a client_credentials access token so it can call the IAM server's authorization API on behalf of your service.
  • Keeps everything typed: TokenPayload, config options and decorators ship with full TypeScript declarations.

Polisafe IAM is a technology of Elegantys. This package is the client-side utility: it consumes the APIs published by a Polisafe IAM server. You still need a running Polisafe IAM instance and an OAuth client registered on it.


How it works

                ┌──────────────────────┐        ┌────────────────────────────┐
  Browser/App ──▶  Your NestJS service │  HTTP  │  Polisafe IAM server       │
  (Bearer JWT)   │  @elegantys/polisafe├───────▶│  • OAuth2 / OIDC token     │
                 │  • jwt-header        │        │  • per-tenant JWKS (/certs)│
                 │  • tenant guard      │        │  • io/authorization check  │
                 │  • privilege guard   │        └────────────────────────────┘
                 └──────────────────────┘
  1. A user signs in against Polisafe IAM and receives an RS256 JWT (OIDC) carrying claims such as sub, tid/tenantId, tenants, optionally permissions, scope, client_id.
  2. Your route declares what it needs with a decorator (@Permission('orders/list')).
  3. The SDK's Passport strategy decodes the token, extracts the tenant, downloads the matching public key from …/polisafe/openid/{tenant}/certs and verifies the signature.
  4. Guards resolve the active tenant and decide allow/deny.
  5. When remote checks are enabled, the SDK authenticates itself with client_credentials and asks the IAM server whether sub may perform {url, method} in its tenant.

Requirements

  • Node.js ≥ 18
  • NestJS 10 (@nestjs/common, @nestjs/core, @nestjs/platform-express)
  • A reachable Polisafe IAM server and an OAuth client registered on it with:
    • the client_credentials grant, and
    • the scopes security:io:authorization (remote checks) and optionally security:io:tenants.

peerDependencies are declared in package.json; npm ≥ 7 installs them automatically in the consuming project.


Installation

# from a registry
npm install @elegantys/polisafe

# or from a local checkout while developing
npm install ../sdk-polisafe

Quick start

Register the module once in your root module:

// app.module.ts
import { Module } from '@nestjs/common';
import { PolisafeSdkModule } from '@elegantys/polisafe';

@Module({
  imports: [
    PolisafeSdkModule.register({
      serviceUrl: process.env.POLISAFE_URL,          // e.g. https://iam.example.com/api
      issuer: process.env.POLISAFE_ISSUER,           // e.g. https://iam.example.com
      client_id: process.env.POLISAFE_CLIENT_ID,     // your client_credentials client
      client_secret: process.env.POLISAFE_CLIENT_SECRET,
      scope: 'security:io:authorization',            // enables remote permission checks
      mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
    }),
  ],
})
export class AppModule {}

Protect a REST controller:

// orders.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Permission, TokenPayload, UserToken } from '@elegantys/polisafe';

@Controller('orders')
@Permission() // class level: every route in the controller is protected
export class OrdersController {
  @Get()
  list(@UserToken() user: TokenPayload) {
    return { user: user.sub, tenant: user.tenant, items: [] };
  }

  @Post()
  @Permission('orders/create') // more specific, overrides nothing: metadata is additive
  create(@Body() body: unknown, @UserToken() user: TokenPayload) {
    return { createdBy: user.id };
  }
}

The SDK registers JwtModule, HttpModule and ScheduleModule internally — you don't have to import anything else.


Configuration options

| Option | Type | Default | Description | |---|---|---|---| | serviceUrl | string | — (required) | Base URL of the Polisafe IAM server (public URL + path prefix), e.g. https://iam.example.com/api | | issuer | string | — (required) | Expected iss claim of incoming tokens, usually the IAM public URL | | client_id | string | — | OAuth client_credentials client id (needed for remote checks) | | client_secret | string | — | OAuth client_credentials client secret (needed for remote checks) | | scope | string | — | Scopes requested for the machine token, e.g. security:io:authorization | | mode | 'production' \| 'development' | 'development' | development enables verbose error messages and debug logging | | permissionCheck | boolean | true | Set to false to disable permission checks entirely — development only! | | checkProviderPermissions | boolean | true | true: ask the IAM server (…/polizei/io/authorization); false: validate against user.permissions embedded in the token | | audience | string | — | Expected audience (currently informational; not enforced by default) | | logging | boolean | false | Reserved for future use | | apiKey | string | — | Deprecated — legacy workspace key, do not use |

Options without defaults are merged over the SDK defaults at registration time, so you only need to pass what differs.


Decorators

@Permission(name?)

Authenticates (jwt-header strategy) → resolves the tenant (TenantGuard) → authorizes the route (PrivilegesGuard).

  • Works at method or class level.
  • name is the permission identifier registered in Polisafe IAM. When omitted, the guard falls back to the request's route path (/orders, POST /orders).
@Get('by-id/:id')
@Permission('orders:read')
read(@UserToken() user: TokenPayload) {
  return user;
}

@Scope(scopes, tokenType?)

Authenticates + tenant + validates OAuth scopes in the token's scope claim, optionally restricting the token type.

// only machine (client_credentials) tokens holding this scope may call this route
@Get('internal/tenants')
@Scope('security:io:tenants', 'client')
listTenants(@UserToken() root: TokenPayload) {
  return { service: root.client_id };
}

tokenType accepts 'user', 'client' or 'all' (default). The SDK tells them apart with sub === client_id.

@UserToken() / @TokenInfo()

Injects the verified token payload with convenience aliases:

@Get('me')
@Permission('profile:read')
me(@UserToken() user: TokenPayload) {
  // user.sub            — subject (string)
  // user.id             — alias of Number(user.sub)
  // user.username       — alias of preferred_username
  // user.tid / .tenant  — active tenant
  // user.tenants        — all tenants the principal belongs to
  // user.is_client      — sub === client_id
}

@TokenInfo() is the same contract (originally used on OIDC userinfo-style endpoints).

@MapTenant(key)

Maps a request parameter (query or body) to one of the principal's tenants, returning 406 if the value is not inside user.tenants. Useful on multi-tenant admin routes to prevent cross-tenant access.

@Delete()
@Permission('orders:delete')
remove(@Body() body: Record<string, any>, @MapTenant('idScope') tenant: string) {
  return { tenant }; // guaranteed to belong to the caller
}

Authorization model

Permission names. In Polisafe IAM, permissions are stored per tenant and assigned to roles → users. The string you pass to @Permission() is matched by name (route.name), and optionally by HTTP method (route.type).

Two check modes (see PrivilegesGuard):

| checkProviderPermissions | Where the decision happens | Requirement | |---|---|---| | true (default) | IAM server — POST …/polizei/io/authorization with the machine token | client_id/client_secret/scope configured; IAM reachable | | false | Your service — token's permissions claims | IAM client with access_token_include_permissions / openid_include_permissions enabled |

Tenant resolution. If the token lists several tenants (user.tenants), the active one comes from the X-Tenant request header and must be present in that list; otherwise the first tenant is used. A token cannot act outside its tenant boundaries.

Development mode. With mode: 'development' and permissionCheck: false, every request is allowed and guards return detailed error messages. Never ship this configuration to production — the SDK logs a warning when it detects it.


Token types & auth flows

User tokens — obtained from Polisafe IAM's OAuth 2.0 / OpenID Connect endpoints (authorization_code, password, implicit, refresh_token). Send them as:

Authorization: Bearer <access_token>

Machine tokens — the SDK obtains its own via the OAuth 2.0 client_credentials grant:

  • POST {serviceUrl}/polisafe/oauth/token with HTTP Basic authentication (client_id:client_secret) and the configured scope.
  • The token is cached in the shared options object and renewed automatically: a scheduled job runs every minute and refreshes it when less than 120 seconds remain.
  • Used as Authorization: Bearer <machine-token> for remote permission checks.

Cookie sessions — for browser flows, the IAM server stores the token in a cookie named a-<client_id>. Use the cookie strategy:

import { JwtAuthCookieGuard } from '@elegantys/polisafe';

@Get('spa')
@UseGuards(JwtAuthCookieGuard)
spa(@UserToken() user: TokenPayload) {
  return user;
}

Remember to enable cookie-parser in your Nest app and to send client_id (query or body) so the SDK can locate the cookie.


Guards & strategies

Everything is exported for reuse or composition:

| Export | Kind | Notes | |---|---|---| | PolisafeSdkModule | module | register(options) returns a @Global dynamic module | | JwtStrategyService | Passport strategy | name 'jwt-header', Bearer token, per-tenant JWKS | | JwtCookieStrategyService | Passport strategy | name 'jwt-cookie', cookie a-<client_id> | | PolizeiSdkService | service | machine client_credentials lifecycle | | JwtAuthGuard / JwtAuthCookieGuard | guards | wrap the two strategies | | TenantGuard, PrivilegesGuard, ScopesGuard | guards | authorization building blocks | | ConfigPolizei, POLIZEI_CONFIG_OPTIONS | config | typed options + NestJS injection token | | TokenPayload | type | claims of a verified Polisafe token |

Compose your own guards if the decorators are not enough:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtAuthGuard, PrivilegesGuard, TenantGuard } from '@elegantys/polisafe';

@Injectable()
export class MyFullAuthGuard extends JwtAuthGuard implements CanActivate {
  // e.g. run TenantGuard + PrivilegesGuard programmatically in canActivate()
}

The strategies verify tokens only against Polisafe IAM's per-tenant JWKS — no shared secrets, no hardcoded keys, and key rotation is handled by jwks-rsa caching.


TypeScript

The package ships declarations. TokenPayload reflects the standard OIDC claims plus Polisafe's tenant/RBAC claims:

interface TokenPayload {
  sub: string;                 // subject
  iss: string; aud: string | string[];
  exp: number; iat: number; jti?: string;
  client_id?: string;
  name?: string; given_name?: string; family_name?: string;
  nickname?: string; preferred_username?: string;
  picture?: string; email?: string; email_verified?: boolean;
  roles?: string[]; permissions?: string[];
  tid?: string; tenant?: string; tenants?: string[];
  id: number;                  // Number(sub)
  is_client?: boolean;         // sub === client_id
}

Need the options elsewhere? Inject them with full typing:

import { Inject } from '@nestjs/common';
import { ConfigPolizei, POLIZEI_CONFIG_OPTIONS } from '@elegantys/polisafe';

export class SomeService {
  constructor(@Inject(POLIZEI_CONFIG_OPTIONS) private readonly options: ConfigPolizei) {}
}

Server API contract

The SDK consumes the following Polisafe IAM endpoints. They are part of the public IAM surface; a future version of the SDK may pin an explicit API version.

| Endpoint | Purpose | |---|---| | POST {serviceUrl}/polisafe/oauth/token (client_credentials) | obtain / renew the machine token | | GET {serviceUrl}/polisafe/openid/{tenant}/certs | per-tenant JWKS used to verify RS256 signatures | | POST {serviceUrl}/polizei/io/authorization | remote permission decision: body { url, method, sub }, 200 = allowed | | GET {serviceUrl}/polizei/io/tenants | (optional) tenant listing for machine clients |

The IAM server must issue tokens containing tid/tenantId and tenants; without them the strategies refuse the token (No tenant identifier found in token).


Troubleshooting

| Symptom | Likely cause / fix | |---|---| | 401 on every protected route | Token missing, expired, wrong issuer, or the IAM server is unreachable for JWKS | | No tenant identifier found in token | Token lacks tid/tenantId — check the IAM client scopes/config | | 401 Tenant boundaries out of scope | X-Tenant not included in user.tenants — omit the header or send a valid tenant | | 403 Forbidden (remote mode) | IAM says the user has no {url, method} permission — grant it in the IAM admin, or client_id/client_secret/scope are not configured | | 403 Forbidden (local mode) | Token has no matching permissions entry — enable access_token_include_permissions on the client | | Everything is allowed | permissionCheck: false (dev) is active — never enable in production | | Errors show only in development | By design: production hides internals; guard messages depend on options.mode |


Development

npm install          # install dev/peer dependencies
npm run build        # tsc -> dist/
npm pack --dry-run   # inspect the publishable tarball

Notes for maintainers:

  • peerDependencies target NestJS 10.
  • The package is published from dist/ only (files: ["dist", "README.md"]).
  • If npm install fails with EALLOWSCRIPTS under npm ≥ 12, unset the injected npm_config_allow_scripts environment variable for the command, or use --ignore-scripts (none of the runtime dependencies require lifecycle scripts).

License

MIT — Polisafe IAM is a technology of Elegantys. This package is an independent client utility and does not include the IAM server.