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/backendNote: @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 {}Option B: Async Configuration (Recommended)
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 configinject?: Array<InjectionToken>- Dependencies to inject into factoryimports?: 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 clientcheckEntitlement(userId, entitlement, resourceId)- Check user entitlementsassignRole(userId, role, resourceType, resourceId)- Assign role to userremoveRole(userId, resourceType, resourceId)- Remove role from userbulkCreateWorkspaces(resources)- Create workspace resources in bulkdeleteWorkspace(resourceId)- Delete workspace resourcebulkCreateEnvironments(resources)- Create environment resources in bulkdeleteEnvironment(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:
- Validate authentication: Check that the user is properly authenticated
- Handle missing data: Throw descriptive errors when required data is missing
- Return consistent format: Always return the user ID as a string
- Consider async operations: Use
async/awaitif 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=30000Error 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 neededMigration 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.