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

@globaltracking/auth-middleware

v3.2.0

Published

Unified authentication and authorization middleware for the Global Tracking platform (Express + NestJS)

Readme

@globaltracking/auth-middleware

Unified authentication and authorization middleware for the Global Tracking platform. Shared by all backend microservices — supports both Express and NestJS.

Features

  • Strategy pattern: Gateway header, trusted headers, JWT — configurable per service
  • NestJS adapter: GtAuthModule, guards, decorators, interceptors, exception filter
  • Express middleware: authenticate, requirePermission, requireRole, requireSelf, requireTenant
  • Hybrid permission resolution: JWT claims (fast) → custom resolver → RBAC HTTP call → deny (fail-closed)
  • Multi-tenant RLS: OrgContextInterceptor sets SET LOCAL app.current_org_id for PostgreSQL Row-Level Security
  • Admin bypass: Configurable adminRoles[] bypass all permission checks
  • TypeScript-first: Full type definitions with Express Request augmentation
  • 98%+ test coverage: 89 tests across strategies, middlewares, and config

Installation

npm install @globaltracking/auth-middleware

Peer dependencies:

| Package | Required for | Optional? | |---------|-------------|-----------| | express ^4.18 | Express middleware | Required | | @nestjs/common ^11.0 | NestJS adapter | Optional | | @nestjs/core ^11.0 | NestJS adapter | Optional | | typeorm ^0.3.0 | OrgContextInterceptor (RLS) | Optional | | rxjs ^7.0 | OrgContextInterceptor | Optional |


NestJS Integration (Recommended)

This is the primary integration path for Global Tracking microservices.

1. Import GtAuthModule in your AppModule

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GtAuthModule } from '@globaltracking/auth-middleware/nestjs';

@Module({
  imports: [
    // ... ConfigModule, TypeOrmModule, ThrottlerModule ...

    GtAuthModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        strategies: ['trusted-headers'],
        internalGatewayToken: config.get('INTERNAL_GATEWAY_TOKEN'),
        adminRoles: ['system_admin', 'org_admin'],
        rbacServiceUrl: config.get('RBAC_SERVICE_URL'),
      }),
    }),

    // ... domain modules ...
  ],
})
export class AppModule {}

What GtAuthModule provides (globally):

| Component | What it does | |-----------|-------------| | GtTrustedHeadersMiddleware | Extracts req.user from configured strategy chain (auto-applied to all routes) | | InternalOnlyGuard | Validates X-Gateway-Token header — attach per-route with @UseGuards() or register as APP_GUARD | | GtPermissionsGuard | Checks @RequirePermissions()auto-registered as APP_GUARD since v3 (opt out with registerGuardGlobally: false) | | OrgContextInterceptor | Sets PostgreSQL RLS context — register as APP_INTERCEPTOR | | AuthExceptionFilter | Catches AuthError and returns standard error envelope |

2. Use decorators in controllers

import {
  RequirePermissions,
  CurrentUser,
  CurrentOrg,
  Public,
  RequireRoles,
} from '@globaltracking/auth-middleware/nestjs';

@Controller('vehicles')
export class VehiclesController {
  @Post()
  @RequirePermissions('vehicles:create')
  create(
    @CurrentOrg() orgId: string,
    @CurrentUser('userId') userId: string,
    @Body() dto: CreateVehicleDto,
  ) {
    // orgId and userId extracted from trusted headers
  }

  @Get(':id')
  @RequirePermissions('vehicles:read')
  findOne(
    @CurrentOrg() orgId: string,
    @Param('id', ParseUUIDPipe) id: string,
  ) { ... }

  @Delete(':id')
  @RequireRoles('system_admin')
  remove(@Param('id', ParseUUIDPipe) id: string) { ... }
}
// Health endpoints skip auth
@Controller('health')
export class HealthController {
  @Get()
  @Public()
  liveness() {
    return { status: 'ok' };
  }
}

3. Register guards and interceptors

Since v3, GtPermissionsGuard is auto-registered as an APP_GUARD by GtAuthModule.forRoot() / forRootAsync(). You only need to wire the interceptor and any service-specific guards:

// app.module.ts providers
import {
  OrgContextInterceptor,
} from '@globaltracking/auth-middleware/nestjs';

providers: [
  { provide: APP_GUARD, useClass: ThrottlerGuard },
  { provide: APP_INTERCEPTOR, useClass: OrgContextInterceptor },
  // ... your other interceptors
],

Opt-out: pass registerGuardGlobally: false if your service composes its own guard chain and wants to attach GtPermissionsGuard manually:

GtAuthModule.forRootAsync({
  registerGuardGlobally: false,
  inject: [ConfigService],
  useFactory: (c: ConfigService) => ({ ... }),
})
// then in providers:
{ provide: APP_GUARD, useClass: MyCompositeGuard }, // wraps Gt checks itself

InternalOnlyGuard is not auto-registered — attach it per-route via @UseGuards(InternalOnlyGuard) on internal-only endpoints, or register it globally if every route in the service is internal-only.

4. Add env vars

INTERNAL_GATEWAY_TOKEN=your-32-char-min-secret
RBAC_SERVICE_URL=http://gt-rbac-service:3000   # optional, for permission resolution

Special case: gt-rbac-service

The RBAC service cannot call itself for permission resolution. Instead, inject its own ResolveService via permissionResolver:

GtAuthModule.forRootAsync({
  imports: [ResolveModule],
  inject: [ConfigService, ResolveService],
  useFactory: (config: ConfigService, resolveService: ResolveService) => ({
    strategies: ['trusted-headers'],
    internalGatewayToken: config.get('INTERNAL_GATEWAY_TOKEN'),
    adminRoles: ['system_admin', 'org_admin'],
    permissionResolver: (orgId, userId, resource, action) =>
      resolveService.checkPermission(orgId, userId, resource, action),
  }),
}),

Express Integration

For services that use plain Express (no NestJS):

import express from 'express';
import {
  initAuth,
  authenticate,
  requirePermission,
  requireTenant,
  authErrorHandler,
} from '@globaltracking/auth-middleware';

const app = express();

// Initialize once at startup
initAuth({
  strategies: ['gateway-header', 'jwt'],
  adminRoles: ['system_admin', 'org_admin'],
  publicKeyPath: './keys/public.pem',
});

// Protected route
app.get('/v1/vehicles',
  authenticate,
  requireTenant,
  requirePermission('vehicles:read'),
  async (req, res) => {
    // req.user.userId, req.user.orgId, etc.
    // req.tenantId set by requireTenant
  }
);

// Error handler (must be last)
app.use(authErrorHandler);

Configuration

initAuth({
  // Strategy chain — tried in order until one matches
  strategies: ['gateway-header', 'jwt'],  // default

  // Gateway header name (GCP API Gateway)
  gatewayHeaderName: 'x-apigateway-api-userinfo',  // default

  // JWT verification (for local dev / non-gateway)
  jwtIssuer: 'globaltracking-auth',  // default
  publicKey: '-----BEGIN PUBLIC KEY-----\n...',  // PEM string
  publicKeyPath: './keys/public.pem',             // or file path

  // Roles that bypass all permission checks
  adminRoles: ['system_admin', 'org_admin'],  // default

  // Trusted headers strategy config
  internalGatewayToken: 'secret',  // also reads env INTERNAL_GATEWAY_TOKEN
  trustedHeaderNames: {
    userId: 'x-user-id',        // default
    orgId: 'x-org-id',          // default
    userRole: 'x-user-role',    // default
    requestId: 'x-request-id',  // default
    gatewayToken: 'x-gateway-token',  // default
  },

  // Permission resolution (NestJS GtPermissionsGuard)
  rbacServiceUrl: 'http://gt-rbac-service:3000',  // HTTP fallback
  permissionResolver: async (orgId, userId, resource, action) => {
    // Custom resolver (e.g., direct DB call)
    return true;
  },
});

Public key resolution order: publicKeypublicKeyPathAUTH_PUBLIC_KEY env → AUTH_PUBLIC_KEY_PATH env.


Auth Strategies

| Strategy | When used | What it reads | |----------|-----------|---------------| | gateway-header | Production (GCP API Gateway) | Base64-encoded JSON in X-Apigateway-Api-Userinfo | | trusted-headers | NestJS services behind API Gateway | X-User-Id, X-Org-Id, X-User-Role, X-Request-Id, X-Gateway-Token | | jwt | Local dev, non-gateway environments | Authorization: Bearer <token> (RS256 verification) |

The strategy chain iterates in the configured order. The first strategy whose canHandle(req) returns true is used.


AuthUser Shape

Every authenticated request gets req.user populated with:

interface AuthUser {
  userId: string;          // from JWT sub or X-User-Id
  email: string;           // from JWT email (empty in trusted-headers mode)
  role: UserRole;          // from JWT role or X-User-Role
  orgId: string;           // from JWT org_id or X-Org-Id
  tenantId: string;        // = orgId in trusted-headers mode
  permissions: string[];   // from JWT claims (empty in trusted-headers mode)
  requestId: string;       // from X-Request-Id
  authSource: AuthStrategy; // 'gateway-header' | 'trusted-headers' | 'jwt'
}

Permission Resolution (GtPermissionsGuard)

The NestJS GtPermissionsGuard resolves permissions in 3 tiers:

  1. JWT claims — If user.permissions is non-empty, check in-memory (fast path)
  2. permissionResolver — If configured, call the function directly (for RBAC service)
  3. RBAC HTTP call — If rbacServiceUrl is configured, POST to /v1/resolve/check
  4. Deny — Fail-closed if no resolution mechanism is available

Admin roles (config.adminRoles) always bypass all checks.


Express Middlewares

| Middleware | Purpose | |-----------|---------| | authenticate | Extracts req.user from strategy chain | | requireRole(...roles) | Checks req.user.role against allowed roles | | requirePermission(...perms) | Requires ALL listed permissions (admin bypass) | | requireAnyPermission(...perms) | Requires AT LEAST ONE permission (admin bypass) | | requireTenant | Ensures req.user.tenantId exists, sets req.tenantId | | requireSelf(paramName?) | Compares req.user.userId with route param (admin bypass) | | authErrorHandler | Error handler for AuthError subclasses |


Error Handling

All auth errors extend AuthError and produce the standard Global Tracking error envelope:

import { UnauthorizedError, ForbiddenError } from '@globaltracking/auth-middleware';

throw new UnauthorizedError('Token expired');
// → 401 { success: false, error: { code: 'UNAUTHORIZED', message: 'Token expired', statusCode: 401 } }

throw new ForbiddenError('Insufficient permissions', { required: ['vehicles:write'] });
// → 403 { success: false, error: { code: 'FORBIDDEN', message: 'Insufficient permissions', statusCode: 403 } }

Utility Functions

import { extractUser, hasRole, hasPermission, hasAnyPermission } from '@globaltracking/auth-middleware';

const user = extractUser(req);            // throws UnauthorizedError if missing
hasRole(user, 'system_admin');            // boolean
hasPermission(user, 'vehicles:read');     // all must match
hasAnyPermission(user, 'a:read', 'b:read'); // at least one

Files Replaced Per Service

When a NestJS service adopts this package, these ~10 files can be deleted:

src/common/decorators/current-org.decorator.ts      → @CurrentOrg from package
src/common/decorators/current-user.decorator.ts     → @CurrentUser from package
src/common/decorators/permissions.decorator.ts      → @RequirePermissions from package
src/common/decorators/public.decorator.ts           → @Public from package
src/common/guards/internal-only.guard.ts            → InternalOnlyGuard from package
src/common/guards/permissions.guard.ts              → GtPermissionsGuard from package
src/common/interceptors/org-context.interceptor.ts  → OrgContextInterceptor from package
src/common/middleware/trusted-headers.middleware.ts  → GtTrustedHeadersMiddleware from package
src/common/interfaces/jwt-payload.interface.ts      → AuthUser from package
src/common/interfaces/request-context.interface.ts  → (Express augmentation from package)

Development

npm test           # run tests with coverage (89 tests, 98%+ coverage)
npm run test:watch # watch mode
npm run build      # compile to dist/
npm run clean      # remove dist/

Migration from v2.x → v3.0

Breaking: GtAuthModule.forRoot() / forRootAsync() now auto-registers GtPermissionsGuard as an APP_GUARD. In v2.x the guard was provided but never globally registered, which meant every @RequirePermissions() decorator was silently inert (routes returned 2xx for users who lacked the declared permission — a platform-wide security defect).

What you need to do:

  1. Remove any existing { provide: APP_GUARD, useClass: GtPermissionsGuard } line from your service's app.module.ts — the library now registers it for you. Leaving it in place only double-runs the guard (still correct, just wasteful).

  2. Audit for endpoints that were accidentally working without the correct permission. Any route missing the right @RequirePermissions(...) decorator will start returning 403 to non-admin users. Grep for missing decorators on controllers that use other guards already; add the decorator or annotate with @Public() as appropriate.

  3. If your service composes its own guard chain (e.g. gt-alert-engine's custom GatewayAuthGuard/OrgIdGuard stack) and you do not want the automatic registration, pass registerGuardGlobally: false:

    GtAuthModule.forRootAsync({
      registerGuardGlobally: false,
      ...
    })

Migration from v1.x

  • superAdminRole → use adminRoles: ['system_admin', 'org_admin'] (legacy superAdminRole still works)
  • RequestUser type alias still exported for backward compatibility
  • Error handler now returns { success: false, error: { code, message, statusCode } } envelope instead of { error: message }
  • extractUser() now sets authSource and requestId on returned user