JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 134
  • Score
    100M100P100Q80778F

NestJS integration library for Blimu authorization and entitlement system

Package Exports

  • @blimu/nestjs
  • @blimu/nestjs/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@blimu/nestjs) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@blimu/nestjs

NestJS integration library for Blimu authorization and entitlement system. This library provides decorators, guards, and services to easily integrate Blimu's authorization-as-a-service into your NestJS applications.

Features

  • 🔐 Entitlement-based authorization - Protect routes with fine-grained entitlement checks
  • 🛡️ Declarative guards - Use decorators to protect endpoints
  • 🔧 Injectable services - Access Blimu Runtime SDK from your services
  • ⚙️ Configurable module - Easy setup with sync/async configuration
  • 🎯 TypeScript support - Full type safety and IntelliSense
  • 🚀 Zero dependencies - Only peer dependencies on NestJS core

Installation

npm install @blimu/nestjs @blimu/backend
# or
yarn add @blimu/nestjs @blimu/backend
# or
pnpm add @blimu/nestjs @blimu/backend

Note: @blimu/backend is a peer dependency and must be installed alongside this library.

Quick Start

1. Configure the Module

Option A: Static Configuration

import { Module } from "@nestjs/common";
import { BlimuModule } from "@blimu/nestjs";

@Module({
  imports: [
    BlimuModule.forRoot({
      apiSecretKey: "your-blimu-api-secret-key",
      baseURL: "https://runtime.blimu.com", // optional
      environmentId: "your-environment-id", // optional
      timeoutMs: 30000, // optional
      getUserId: (req) => req.user?.id, // Extract user ID from request
    }),
  ],
})
export class AppModule {}
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { BlimuModule } from "@blimu/nestjs";

@Module({
  imports: [
    ConfigModule.forRoot(),
    BlimuModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        apiSecretKey: configService.get("BLIMU_API_SECRET_KEY"),
        baseURL: configService.get("BLIMU_BASE_URL"),
        environmentId: configService.get("BLIMU_ENVIRONMENT_ID"),
        timeoutMs: parseInt(configService.get("BLIMU_TIMEOUT_MS", "30000")),
        getUserId: (req) => req.user?.id, // Extract user ID from request
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

2. Protect Routes with Entitlements

import { Controller, Get, Param } from "@nestjs/common";
import { Entitlement } from "@blimu/nestjs";

@Controller("workspaces")
export class WorkspaceController {
  @Get(":workspaceId")
  @Entitlement("workspace:read", (req) => req.params.workspaceId)
  async getWorkspace(@Param("workspaceId") workspaceId: string) {
    // User is guaranteed to have 'workspace:read' entitlement on this workspace
    return { id: workspaceId, name: "My Workspace" };
  }

  @Delete(":workspaceId")
  @Entitlement("workspace:delete", (req) => req.params.workspaceId)
  async deleteWorkspace(@Param("workspaceId") workspaceId: string) {
    // User is guaranteed to have 'workspace:delete' entitlement on this workspace
    return { success: true };
  }
}

3. Use Blimu Runtime SDK in Services

import { Injectable } from "@nestjs/common";
import { BlimuRuntimeService } from "@blimu/nestjs";

@Injectable()
export class UserService {
  constructor(private readonly blimuRuntime: BlimuRuntimeService) {}

  async assignUserToWorkspace(
    userId: string,
    workspaceId: string,
    role: string
  ) {
    // Assign a role to a user on a workspace
    return await this.blimuRuntime.assignRole(
      userId,
      role,
      "workspace",
      workspaceId
    );
  }

  async checkUserPermission(
    userId: string,
    entitlement: string,
    resourceId: string
  ) {
    // Check if user has specific entitlement
    const result = await this.blimuRuntime.checkEntitlement(
      userId,
      entitlement,
      resourceId
    );

    return result.allowed;
  }

  async createWorkspaces(
    workspaces: Array<{ id: string; extraFields?: Record<string, unknown> }>
  ) {
    // Create workspace resources in bulk
    return await this.blimuRuntime.bulkCreateWorkspaces(workspaces);
  }

  async deleteWorkspace(workspaceId: string) {
    // Delete a workspace resource
    return await this.blimuRuntime.deleteWorkspace(workspaceId);
  }
}

API Reference

BlimuModule

The main module that provides Blimu integration.

BlimuModule.forRoot(config: BlimuConfig)

Configure the module with static configuration.

BlimuModule.forRootAsync(options)

Configure the module with async configuration.

Options:

  • useFactory: (...args) => BlimuConfig | Promise<BlimuConfig> - Factory function to create config
  • inject?: Array<InjectionToken> - Dependencies to inject into factory
  • imports?: Array<Module> - Modules to import

@Entitlement Decorator

Protects routes with entitlement checks.

@Entitlement(entitlementKey, resourceIdExtractor)

Parameters:

  • entitlementKey: string - The entitlement to check (e.g., 'workspace:read')
  • resourceIdExtractor: (req) => string | Promise<string> - Function to extract resource ID from request

Examples:

// Simple path parameter
@Entitlement('workspace:read', (req) => req.params.workspaceId)

// Complex extraction
@Entitlement('organization:create_workspace', (req) => {
  return req.params.organizationId;
})

// Async extraction (e.g., from database)
@Entitlement('workspace:delete_item', async (req) => {
  const item = await itemService.findById(req.params.itemId);
  return item.workspaceId;
})

BlimuRuntimeService

Injectable service that provides access to Blimu Runtime SDK.

Methods

  • getClient(): BlimuRuntime - Get the configured Blimu Runtime client
  • checkEntitlement(userId, entitlement, resourceId) - Check user entitlements
  • assignRole(userId, role, resourceType, resourceId) - Assign role to user
  • removeRole(userId, resourceType, resourceId) - Remove role from user
  • bulkCreateWorkspaces(resources) - Create workspace resources in bulk
  • deleteWorkspace(resourceId) - Delete workspace resource
  • bulkCreateEnvironments(resources) - Create environment resources in bulk
  • deleteEnvironment(resourceId) - Delete environment resource

Configuration

BlimuConfig Interface

interface BlimuConfig<TRequest extends Request = Request> {
  apiSecretKey: string; // Required: Your Blimu API secret key
  baseURL?: string; // Optional: Blimu Runtime API URL (default: https://runtime.blimu.com)
  environmentId?: string; // Optional: Environment ID for future use
  timeoutMs?: number; // Optional: Request timeout (default: 30000)
  getUserId: (request: TRequest) => string | Promise<string>; // Required: Function to extract user ID from request
}

Types

The library provides several pre-built request interfaces, but you can also define your own:

Custom Request Types

You can define your own request interface:

interface MyCustomRequest extends Request {
  user: {
    id: string;
    email: string;
    roles: string[];
    organizationId: string;
  };
  session: {
    id: string;
    expiresAt: Date;
  };
}

Advanced Usage

Using Custom Request Types

The Blimu module supports generic request types for better type safety. This allows you to define your own request interface and get full TypeScript support throughout the library.

Basic Usage with Custom Request Type

import { Request } from "express";
import { BlimuModule, Entitlement } from "@blimu/nestjs";

// Define your custom request interface
interface MyAuthenticatedRequest extends Request {
  user: {
    id: string;
    email: string;
    organizationId: string;
    roles: string[];
  };
  sessionId: string;
}

// Configure the module with your custom type
@Module({
  imports: [
    BlimuModule.forRoot<MyAuthenticatedRequest>({
      apiSecretKey: "your-api-secret-key",
      getUserId: (req) => req.user.id, // req is typed as MyAuthenticatedRequest
    }),
  ],
})
export class AppModule {}

// Use in controllers with full type safety
@Controller("workspaces")
export class WorkspaceController {
  @Get(":workspaceId")
  @Entitlement<MyAuthenticatedRequest>("workspace:read", (req) => {
    // req is properly typed, so you get IntelliSense and type checking
    console.log(req.user.organizationId); // TypeScript knows this exists
    console.log(req.sessionId); // This too!
    return req.params.workspaceId;
  })
  async getWorkspace(@Param("workspaceId") workspaceId: string) {
    return { id: workspaceId, name: "My Workspace" };
  }
}

Using Pre-built Request Types

The library provides several pre-built request interfaces:

import {
  BlimuModule,
  AuthenticatedRequest,
  StrictAuthenticatedRequest,
  Entitlement,
} from "@blimu/nestjs";

// Using AuthenticatedRequest (user is optional)
BlimuModule.forRoot<AuthenticatedRequest>({
  apiSecretKey: "your-key",
  getUserId: (req) => req.user?.id || "", // Need optional chaining
});

// Using StrictAuthenticatedRequest (user is always present)
BlimuModule.forRoot<StrictAuthenticatedRequest>({
  apiSecretKey: "your-key",
  getUserId: (req) => req.user.id, // No optional chaining needed
});

Async Configuration with Custom Types

interface MyRequest extends Request {
  user: { id: string; tenantId: string };
}

@Module({
  imports: [
    BlimuModule.forRootAsync<MyRequest>({
      useFactory: (configService: ConfigService) => ({
        apiSecretKey: configService.get("BLIMU_API_SECRET_KEY"),
        getUserId: (req) => req.user.id, // Fully typed
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Configuring User ID Extraction

The getUserId function is a critical part of the Blimu configuration that determines how user IDs are extracted from incoming requests. This function is called by the @Entitlement decorator to identify which user to check entitlements for.

Common Patterns

Passport.js Integration:

BlimuModule.forRoot({
  // ... other config
  getUserId: (req) => {
    if (!req.user?.id) {
      throw new Error("User not authenticated");
    }
    return req.user.id;
  },
});

JWT Token Extraction:

import * as jwt from "jsonwebtoken";

BlimuModule.forRoot({
  // ... other config
  getUserId: (req) => {
    const token = req.headers.authorization?.replace("Bearer ", "");
    if (!token) {
      throw new Error("No authorization token provided");
    }

    const decoded = jwt.verify(token, process.env.JWT_SECRET) as any;
    return decoded.sub || decoded.userId;
  },
});

Custom Header:

BlimuModule.forRoot({
  // ... other config
  getUserId: (req) => {
    const userId = req.headers["x-user-id"] as string;
    if (!userId) {
      throw new Error("User ID header missing");
    }
    return userId;
  },
});

Async Database Lookup:

BlimuModule.forRootAsync({
  useFactory: (userService: UserService) => ({
    // ... other config
    getUserId: async (req) => {
      const sessionId = req.headers["x-session-id"] as string;
      const user = await userService.findBySessionId(sessionId);
      if (!user) {
        throw new Error("Invalid session");
      }
      return user.id;
    },
  }),
  inject: [UserService],
});

API Key Authentication:

BlimuModule.forRoot({
  // ... other config
  getUserId: async (req) => {
    const apiKey = req.headers["x-api-key"] as string;
    if (!apiKey) {
      throw new Error("API key required");
    }

    // Look up user by API key
    const user = await apiKeyService.findUserByKey(apiKey);
    if (!user) {
      throw new Error("Invalid API key");
    }

    return user.id;
  },
});

Error Handling

If the getUserId function throws an error or returns a falsy value, the entitlement check will fail with a ForbiddenException. Make sure to:

  1. Validate authentication: Check that the user is properly authenticated
  2. Handle missing data: Throw descriptive errors when required data is missing
  3. Return consistent format: Always return the user ID as a string
  4. Consider async operations: Use async/await if you need to perform database lookups

Custom Resource ID Extraction

For complex scenarios where the resource ID isn't directly in the request parameters:

@Controller("projects")
export class ProjectController {
  constructor(private readonly projectService: ProjectService) {}

  @Delete(":projectId/items/:itemId")
  @Entitlement("project:delete_item", async (req) => {
    // Extract project ID from item
    const item = await this.projectService.findItemById(req.params.itemId);
    return item.projectId;
  })
  async deleteItem(@Param("itemId") itemId: string) {
    // Implementation
  }
}

Using Multiple Entitlements

You can stack multiple entitlement decorators:

@Get(':workspaceId/admin-panel')
@Entitlement('workspace:admin', (req) => req.params.workspaceId)
@Entitlement('feature:admin_panel', (req) => req.params.workspaceId)
async getAdminPanel(@Param('workspaceId') workspaceId: string) {
  // User needs both entitlements
}

Direct SDK Usage

Access the full Blimu Runtime SDK for advanced operations:

@Injectable()
export class AdvancedService {
  constructor(private readonly blimuRuntime: BlimuRuntimeService) {}

  async complexOperation() {
    const client = this.blimuRuntime.getClient();

    // Use any SDK method
    const users = await client.users.list({ resourceId: "workspace123" });
    const roles = await client.roles.list({ userId: "user456" });

    // Batch operations, etc.
  }
}

Environment Variables

For production deployments, use environment variables:

BLIMU_API_SECRET_KEY=your-secret-key
BLIMU_BASE_URL=https://runtime.blimu.com
BLIMU_ENVIRONMENT_ID=your-environment-id
BLIMU_TIMEOUT_MS=30000

Error Handling

The library throws ForbiddenException when:

  • User is not authenticated
  • User doesn't have required entitlement
  • Resource ID cannot be extracted
  • Blimu API is unreachable
import { ForbiddenException } from "@nestjs/common";

// This is automatically handled by the @Entitlement decorator
// But you can catch it in your exception filters if needed

Migration from Platform-Specific Code

If you're migrating from platform-specific entitlement code:

Before (Platform-specific)

import { NestBlimuModule } from "./entitlement/entitlement.module";
import { Entitlement } from "./entitlement/entitlement.decorator";

After (Library)

import { BlimuModule, Entitlement } from "@blimu/nestjs";

The API is identical, just import from the library instead.

Contributing

This library is part of the Blimu ecosystem. For issues and contributions, please refer to the main Blimu repository.

License

MIT License - see LICENSE file for details.