@blimu/nestjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +543 -0
- package/dist/config/blimu.config.d.ts +10 -0
- package/dist/config/blimu.config.d.ts.map +1 -0
- package/dist/config/blimu.config.js +5 -0
- package/dist/config/blimu.config.js.map +1 -0
- package/dist/decorators/entitlement.decorator.d.ts +4 -0
- package/dist/decorators/entitlement.decorator.d.ts.map +1 -0
- package/dist/decorators/entitlement.decorator.js +10 -0
- package/dist/decorators/entitlement.decorator.js.map +1 -0
- package/dist/guards/entitlement.guard.d.ts +20 -0
- package/dist/guards/entitlement.guard.d.ts.map +1 -0
- package/dist/guards/entitlement.guard.js +80 -0
- package/dist/guards/entitlement.guard.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/blimu.module.d.ts +12 -0
- package/dist/modules/blimu.module.d.ts.map +1 -0
- package/dist/modules/blimu.module.js +85 -0
- package/dist/modules/blimu.module.js.map +1 -0
- package/dist/services/blimu-runtime.service.d.ts +149 -0
- package/dist/services/blimu-runtime.service.d.ts.map +1 -0
- package/dist/services/blimu-runtime.service.js +199 -0
- package/dist/services/blimu-runtime.service.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/request.types.d.ts +31 -0
- package/dist/types/request.types.d.ts.map +1 -0
- package/dist/types/request.types.js +2 -0
- package/dist/types/request.types.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# @blimu/nestjs-blimu
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Entitlement-based authorization** - Protect routes with fine-grained entitlement checks
|
|
8
|
+
- 🛡️ **Declarative guards** - Use decorators to protect endpoints
|
|
9
|
+
- 🔧 **Injectable services** - Access Blimu Runtime SDK from your services
|
|
10
|
+
- ⚙️ **Configurable module** - Easy setup with sync/async configuration
|
|
11
|
+
- 🎯 **TypeScript support** - Full type safety and IntelliSense
|
|
12
|
+
- 🚀 **Zero dependencies** - Only peer dependencies on NestJS core
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @blimu/nestjs-blimu @blimu/runtime-sdk
|
|
18
|
+
# or
|
|
19
|
+
yarn add @blimu/nestjs-blimu @blimu/runtime-sdk
|
|
20
|
+
# or
|
|
21
|
+
pnpm add @blimu/nestjs-blimu @blimu/runtime-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Note:** `@blimu/runtime-sdk` is a peer dependency and must be installed alongside this library.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Configure the Module
|
|
29
|
+
|
|
30
|
+
#### Option A: Static Configuration
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { Module } from '@nestjs/common';
|
|
34
|
+
import { BlimuModule } from '@blimu/nestjs-blimu';
|
|
35
|
+
|
|
36
|
+
@Module({
|
|
37
|
+
imports: [
|
|
38
|
+
BlimuModule.forRoot({
|
|
39
|
+
apiSecretKey: 'your-blimu-api-secret-key',
|
|
40
|
+
baseURL: 'https://runtime.blimu.com', // optional
|
|
41
|
+
environmentId: 'your-environment-id', // optional
|
|
42
|
+
timeoutMs: 30000, // optional
|
|
43
|
+
getUserId: (req) => req.user?.id, // Extract user ID from request
|
|
44
|
+
}),
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
export class AppModule {}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### Option B: Async Configuration (Recommended)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Module } from '@nestjs/common';
|
|
54
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
55
|
+
import { BlimuModule } from '@blimu/nestjs-blimu';
|
|
56
|
+
|
|
57
|
+
@Module({
|
|
58
|
+
imports: [
|
|
59
|
+
ConfigModule.forRoot(),
|
|
60
|
+
BlimuModule.forRootAsync({
|
|
61
|
+
useFactory: (configService: ConfigService) => ({
|
|
62
|
+
apiSecretKey: configService.get('BLIMU_API_SECRET_KEY'),
|
|
63
|
+
baseURL: configService.get('BLIMU_BASE_URL'),
|
|
64
|
+
environmentId: configService.get('BLIMU_ENVIRONMENT_ID'),
|
|
65
|
+
timeoutMs: parseInt(configService.get('BLIMU_TIMEOUT_MS', '30000')),
|
|
66
|
+
getUserId: (req) => req.user?.id, // Extract user ID from request
|
|
67
|
+
}),
|
|
68
|
+
inject: [ConfigService],
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
export class AppModule {}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Protect Routes with Entitlements
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { Controller, Get, Param } from '@nestjs/common';
|
|
79
|
+
import { Entitlement } from '@blimu/nestjs-blimu';
|
|
80
|
+
|
|
81
|
+
@Controller('workspaces')
|
|
82
|
+
export class WorkspaceController {
|
|
83
|
+
@Get(':workspaceId')
|
|
84
|
+
@Entitlement('workspace:read', (req) => req.params.workspaceId)
|
|
85
|
+
async getWorkspace(@Param('workspaceId') workspaceId: string) {
|
|
86
|
+
// User is guaranteed to have 'workspace:read' entitlement on this workspace
|
|
87
|
+
return { id: workspaceId, name: 'My Workspace' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Delete(':workspaceId')
|
|
91
|
+
@Entitlement('workspace:delete', (req) => req.params.workspaceId)
|
|
92
|
+
async deleteWorkspace(@Param('workspaceId') workspaceId: string) {
|
|
93
|
+
// User is guaranteed to have 'workspace:delete' entitlement on this workspace
|
|
94
|
+
return { success: true };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Use Blimu Runtime SDK in Services
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Injectable } from '@nestjs/common';
|
|
103
|
+
import { BlimuRuntimeService } from '@blimu/nestjs-blimu';
|
|
104
|
+
|
|
105
|
+
@Injectable()
|
|
106
|
+
export class UserService {
|
|
107
|
+
constructor(private readonly blimuRuntime: BlimuRuntimeService) {}
|
|
108
|
+
|
|
109
|
+
async assignUserToWorkspace(userId: string, workspaceId: string, role: string) {
|
|
110
|
+
// Assign a role to a user on a workspace
|
|
111
|
+
return await this.blimuRuntime.assignRole(userId, role, 'workspace', workspaceId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async checkUserPermission(userId: string, entitlement: string, resourceId: string) {
|
|
115
|
+
// Check if user has specific entitlement
|
|
116
|
+
const result = await this.blimuRuntime.checkEntitlement(userId, entitlement, resourceId);
|
|
117
|
+
|
|
118
|
+
return result.allowed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async createWorkspaces(workspaces: Array<{ id: string; extraFields?: Record<string, unknown> }>) {
|
|
122
|
+
// Create workspace resources in bulk
|
|
123
|
+
return await this.blimuRuntime.bulkCreateWorkspaces(workspaces);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async deleteWorkspace(workspaceId: string) {
|
|
127
|
+
// Delete a workspace resource
|
|
128
|
+
return await this.blimuRuntime.deleteWorkspace(workspaceId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
### BlimuModule
|
|
136
|
+
|
|
137
|
+
The main module that provides Blimu integration.
|
|
138
|
+
|
|
139
|
+
#### `BlimuModule.forRoot(config: BlimuConfig)`
|
|
140
|
+
|
|
141
|
+
Configure the module with static configuration.
|
|
142
|
+
|
|
143
|
+
#### `BlimuModule.forRootAsync(options)`
|
|
144
|
+
|
|
145
|
+
Configure the module with async configuration.
|
|
146
|
+
|
|
147
|
+
**Options:**
|
|
148
|
+
|
|
149
|
+
- `useFactory: (...args) => BlimuConfig | Promise<BlimuConfig>` - Factory function to create config
|
|
150
|
+
- `inject?: Array<InjectionToken>` - Dependencies to inject into factory
|
|
151
|
+
- `imports?: Array<Module>` - Modules to import
|
|
152
|
+
|
|
153
|
+
### @Entitlement Decorator
|
|
154
|
+
|
|
155
|
+
Protects routes with entitlement checks.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
@Entitlement(entitlementKey, resourceIdExtractor)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Parameters:**
|
|
162
|
+
|
|
163
|
+
- `entitlementKey: string` - The entitlement to check (e.g., 'workspace:read')
|
|
164
|
+
- `resourceIdExtractor: (req) => string | Promise<string>` - Function to extract resource ID from request
|
|
165
|
+
|
|
166
|
+
**Examples:**
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Simple path parameter
|
|
170
|
+
@Entitlement('workspace:read', (req) => req.params.workspaceId)
|
|
171
|
+
|
|
172
|
+
// Complex extraction
|
|
173
|
+
@Entitlement('organization:create_workspace', (req) => {
|
|
174
|
+
return req.params.organizationId;
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Async extraction (e.g., from database)
|
|
178
|
+
@Entitlement('workspace:delete_item', async (req) => {
|
|
179
|
+
const item = await itemService.findById(req.params.itemId);
|
|
180
|
+
return item.workspaceId;
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### BlimuRuntimeService
|
|
185
|
+
|
|
186
|
+
Injectable service that provides access to Blimu Runtime SDK.
|
|
187
|
+
|
|
188
|
+
#### Methods
|
|
189
|
+
|
|
190
|
+
- `getClient(): BlimuRuntime` - Get the configured Blimu Runtime client
|
|
191
|
+
- `checkEntitlement(userId, entitlement, resourceId)` - Check user entitlements
|
|
192
|
+
- `assignRole(userId, role, resourceType, resourceId)` - Assign role to user
|
|
193
|
+
- `removeRole(userId, resourceType, resourceId)` - Remove role from user
|
|
194
|
+
- `bulkCreateWorkspaces(resources)` - Create workspace resources in bulk
|
|
195
|
+
- `deleteWorkspace(resourceId)` - Delete workspace resource
|
|
196
|
+
- `bulkCreateEnvironments(resources)` - Create environment resources in bulk
|
|
197
|
+
- `deleteEnvironment(resourceId)` - Delete environment resource
|
|
198
|
+
|
|
199
|
+
### Configuration
|
|
200
|
+
|
|
201
|
+
#### BlimuConfig Interface
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
interface BlimuConfig<TRequest extends Request = Request> {
|
|
205
|
+
apiSecretKey: string; // Required: Your Blimu API secret key
|
|
206
|
+
baseURL?: string; // Optional: Blimu Runtime API URL (default: https://runtime.blimu.com)
|
|
207
|
+
environmentId?: string; // Optional: Environment ID for future use
|
|
208
|
+
timeoutMs?: number; // Optional: Request timeout (default: 30000)
|
|
209
|
+
getUserId: (request: TRequest) => string | Promise<string>; // Required: Function to extract user ID from request
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Types
|
|
214
|
+
|
|
215
|
+
The library provides several pre-built request interfaces, but you can also define your own:
|
|
216
|
+
|
|
217
|
+
#### Custom Request Types
|
|
218
|
+
|
|
219
|
+
You can define your own request interface:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
interface MyCustomRequest extends Request {
|
|
223
|
+
user: {
|
|
224
|
+
id: string;
|
|
225
|
+
email: string;
|
|
226
|
+
roles: string[];
|
|
227
|
+
organizationId: string;
|
|
228
|
+
};
|
|
229
|
+
session: {
|
|
230
|
+
id: string;
|
|
231
|
+
expiresAt: Date;
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Advanced Usage
|
|
237
|
+
|
|
238
|
+
### Using Custom Request Types
|
|
239
|
+
|
|
240
|
+
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.
|
|
241
|
+
|
|
242
|
+
#### Basic Usage with Custom Request Type
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { Request } from 'express';
|
|
246
|
+
import { BlimuModule, Entitlement } from '@blimu/nestjs-blimu';
|
|
247
|
+
|
|
248
|
+
// Define your custom request interface
|
|
249
|
+
interface MyAuthenticatedRequest extends Request {
|
|
250
|
+
user: {
|
|
251
|
+
id: string;
|
|
252
|
+
email: string;
|
|
253
|
+
organizationId: string;
|
|
254
|
+
roles: string[];
|
|
255
|
+
};
|
|
256
|
+
sessionId: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Configure the module with your custom type
|
|
260
|
+
@Module({
|
|
261
|
+
imports: [
|
|
262
|
+
BlimuModule.forRoot<MyAuthenticatedRequest>({
|
|
263
|
+
apiSecretKey: 'your-api-secret-key',
|
|
264
|
+
getUserId: (req) => req.user.id, // req is typed as MyAuthenticatedRequest
|
|
265
|
+
}),
|
|
266
|
+
],
|
|
267
|
+
})
|
|
268
|
+
export class AppModule {}
|
|
269
|
+
|
|
270
|
+
// Use in controllers with full type safety
|
|
271
|
+
@Controller('workspaces')
|
|
272
|
+
export class WorkspaceController {
|
|
273
|
+
@Get(':workspaceId')
|
|
274
|
+
@Entitlement<MyAuthenticatedRequest>('workspace:read', (req) => {
|
|
275
|
+
// req is properly typed, so you get IntelliSense and type checking
|
|
276
|
+
console.log(req.user.organizationId); // TypeScript knows this exists
|
|
277
|
+
console.log(req.sessionId); // This too!
|
|
278
|
+
return req.params.workspaceId;
|
|
279
|
+
})
|
|
280
|
+
async getWorkspace(@Param('workspaceId') workspaceId: string) {
|
|
281
|
+
return { id: workspaceId, name: 'My Workspace' };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### Using Pre-built Request Types
|
|
287
|
+
|
|
288
|
+
The library provides several pre-built request interfaces:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import {
|
|
292
|
+
BlimuModule,
|
|
293
|
+
AuthenticatedRequest,
|
|
294
|
+
StrictAuthenticatedRequest,
|
|
295
|
+
Entitlement,
|
|
296
|
+
} from '@blimu/nestjs-blimu';
|
|
297
|
+
|
|
298
|
+
// Using AuthenticatedRequest (user is optional)
|
|
299
|
+
BlimuModule.forRoot<AuthenticatedRequest>({
|
|
300
|
+
apiSecretKey: 'your-key',
|
|
301
|
+
getUserId: (req) => req.user?.id || '', // Need optional chaining
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Using StrictAuthenticatedRequest (user is always present)
|
|
305
|
+
BlimuModule.forRoot<StrictAuthenticatedRequest>({
|
|
306
|
+
apiSecretKey: 'your-key',
|
|
307
|
+
getUserId: (req) => req.user.id, // No optional chaining needed
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### Async Configuration with Custom Types
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
interface MyRequest extends Request {
|
|
315
|
+
user: { id: string; tenantId: string };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@Module({
|
|
319
|
+
imports: [
|
|
320
|
+
BlimuModule.forRootAsync<MyRequest>({
|
|
321
|
+
useFactory: (configService: ConfigService) => ({
|
|
322
|
+
apiSecretKey: configService.get('BLIMU_API_SECRET_KEY'),
|
|
323
|
+
getUserId: (req) => req.user.id, // Fully typed
|
|
324
|
+
}),
|
|
325
|
+
inject: [ConfigService],
|
|
326
|
+
}),
|
|
327
|
+
],
|
|
328
|
+
})
|
|
329
|
+
export class AppModule {}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Configuring User ID Extraction
|
|
333
|
+
|
|
334
|
+
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.
|
|
335
|
+
|
|
336
|
+
#### Common Patterns
|
|
337
|
+
|
|
338
|
+
**Passport.js Integration:**
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
BlimuModule.forRoot({
|
|
342
|
+
// ... other config
|
|
343
|
+
getUserId: (req) => {
|
|
344
|
+
if (!req.user?.id) {
|
|
345
|
+
throw new Error('User not authenticated');
|
|
346
|
+
}
|
|
347
|
+
return req.user.id;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**JWT Token Extraction:**
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import * as jwt from 'jsonwebtoken';
|
|
356
|
+
|
|
357
|
+
BlimuModule.forRoot({
|
|
358
|
+
// ... other config
|
|
359
|
+
getUserId: (req) => {
|
|
360
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
361
|
+
if (!token) {
|
|
362
|
+
throw new Error('No authorization token provided');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET) as any;
|
|
366
|
+
return decoded.sub || decoded.userId;
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Custom Header:**
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
BlimuModule.forRoot({
|
|
375
|
+
// ... other config
|
|
376
|
+
getUserId: (req) => {
|
|
377
|
+
const userId = req.headers['x-user-id'] as string;
|
|
378
|
+
if (!userId) {
|
|
379
|
+
throw new Error('User ID header missing');
|
|
380
|
+
}
|
|
381
|
+
return userId;
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Async Database Lookup:**
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
BlimuModule.forRootAsync({
|
|
390
|
+
useFactory: (userService: UserService) => ({
|
|
391
|
+
// ... other config
|
|
392
|
+
getUserId: async (req) => {
|
|
393
|
+
const sessionId = req.headers['x-session-id'] as string;
|
|
394
|
+
const user = await userService.findBySessionId(sessionId);
|
|
395
|
+
if (!user) {
|
|
396
|
+
throw new Error('Invalid session');
|
|
397
|
+
}
|
|
398
|
+
return user.id;
|
|
399
|
+
},
|
|
400
|
+
}),
|
|
401
|
+
inject: [UserService],
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**API Key Authentication:**
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
BlimuModule.forRoot({
|
|
409
|
+
// ... other config
|
|
410
|
+
getUserId: async (req) => {
|
|
411
|
+
const apiKey = req.headers['x-api-key'] as string;
|
|
412
|
+
if (!apiKey) {
|
|
413
|
+
throw new Error('API key required');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Look up user by API key
|
|
417
|
+
const user = await apiKeyService.findUserByKey(apiKey);
|
|
418
|
+
if (!user) {
|
|
419
|
+
throw new Error('Invalid API key');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return user.id;
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Error Handling
|
|
428
|
+
|
|
429
|
+
If the `getUserId` function throws an error or returns a falsy value, the entitlement check will fail with a `ForbiddenException`. Make sure to:
|
|
430
|
+
|
|
431
|
+
1. **Validate authentication**: Check that the user is properly authenticated
|
|
432
|
+
2. **Handle missing data**: Throw descriptive errors when required data is missing
|
|
433
|
+
3. **Return consistent format**: Always return the user ID as a string
|
|
434
|
+
4. **Consider async operations**: Use `async/await` if you need to perform database lookups
|
|
435
|
+
|
|
436
|
+
### Custom Resource ID Extraction
|
|
437
|
+
|
|
438
|
+
For complex scenarios where the resource ID isn't directly in the request parameters:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
@Controller('projects')
|
|
442
|
+
export class ProjectController {
|
|
443
|
+
constructor(private readonly projectService: ProjectService) {}
|
|
444
|
+
|
|
445
|
+
@Delete(':projectId/items/:itemId')
|
|
446
|
+
@Entitlement('project:delete_item', async (req) => {
|
|
447
|
+
// Extract project ID from item
|
|
448
|
+
const item = await this.projectService.findItemById(req.params.itemId);
|
|
449
|
+
return item.projectId;
|
|
450
|
+
})
|
|
451
|
+
async deleteItem(@Param('itemId') itemId: string) {
|
|
452
|
+
// Implementation
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Using Multiple Entitlements
|
|
458
|
+
|
|
459
|
+
You can stack multiple entitlement decorators:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
@Get(':workspaceId/admin-panel')
|
|
463
|
+
@Entitlement('workspace:admin', (req) => req.params.workspaceId)
|
|
464
|
+
@Entitlement('feature:admin_panel', (req) => req.params.workspaceId)
|
|
465
|
+
async getAdminPanel(@Param('workspaceId') workspaceId: string) {
|
|
466
|
+
// User needs both entitlements
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Direct SDK Usage
|
|
471
|
+
|
|
472
|
+
Access the full Blimu Runtime SDK for advanced operations:
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
@Injectable()
|
|
476
|
+
export class AdvancedService {
|
|
477
|
+
constructor(private readonly blimuRuntime: BlimuRuntimeService) {}
|
|
478
|
+
|
|
479
|
+
async complexOperation() {
|
|
480
|
+
const client = this.blimuRuntime.getClient();
|
|
481
|
+
|
|
482
|
+
// Use any SDK method
|
|
483
|
+
const users = await client.users.list({ resourceId: 'workspace123' });
|
|
484
|
+
const roles = await client.roles.list({ userId: 'user456' });
|
|
485
|
+
|
|
486
|
+
// Batch operations, etc.
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Environment Variables
|
|
492
|
+
|
|
493
|
+
For production deployments, use environment variables:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
BLIMU_API_SECRET_KEY=your-secret-key
|
|
497
|
+
BLIMU_BASE_URL=https://runtime.blimu.com
|
|
498
|
+
BLIMU_ENVIRONMENT_ID=your-environment-id
|
|
499
|
+
BLIMU_TIMEOUT_MS=30000
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Error Handling
|
|
503
|
+
|
|
504
|
+
The library throws `ForbiddenException` when:
|
|
505
|
+
|
|
506
|
+
- User is not authenticated
|
|
507
|
+
- User doesn't have required entitlement
|
|
508
|
+
- Resource ID cannot be extracted
|
|
509
|
+
- Blimu API is unreachable
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { ForbiddenException } from '@nestjs/common';
|
|
513
|
+
|
|
514
|
+
// This is automatically handled by the @Entitlement decorator
|
|
515
|
+
// But you can catch it in your exception filters if needed
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## Migration from Platform-Specific Code
|
|
519
|
+
|
|
520
|
+
If you're migrating from platform-specific entitlement code:
|
|
521
|
+
|
|
522
|
+
### Before (Platform-specific)
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { NestBlimuModule } from './entitlement/entitlement.module';
|
|
526
|
+
import { Entitlement } from './entitlement/entitlement.decorator';
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### After (Library)
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { BlimuModule, Entitlement } from '@blimu/nestjs-blimu';
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
The API is identical, just import from the library instead.
|
|
536
|
+
|
|
537
|
+
## Contributing
|
|
538
|
+
|
|
539
|
+
This library is part of the Blimu ecosystem. For issues and contributions, please refer to the main Blimu repository.
|
|
540
|
+
|
|
541
|
+
## License
|
|
542
|
+
|
|
543
|
+
MIT License - see LICENSE file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Request } from 'express';
|
|
2
|
+
export interface BlimuConfig<TRequest extends Request = Request> {
|
|
3
|
+
apiSecretKey: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
environmentId?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
getUserId: (request: TRequest) => string | Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
export declare const BLIMU_CONFIG: unique symbol;
|
|
10
|
+
//# sourceMappingURL=blimu.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blimu.config.d.ts","sourceRoot":"","sources":["../../src/config/blimu.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAKvC,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,OAAO,GAAG,OAAO;IAI7D,YAAY,EAAE,MAAM,CAAC;IAMrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAMjB,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB,SAAS,CAAC,EAAE,MAAM,CAAC;IA2BnB,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D;AAKD,eAAO,MAAM,YAAY,eAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blimu.config.js","sourceRoot":"","sources":["../../src/config/blimu.config.ts"],"names":[],"mappings":";;;AA4Da,QAAA,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Request } from 'express';
|
|
2
|
+
import { Schema } from '@blimu/runtime-sdk';
|
|
3
|
+
export declare const Entitlement: <TRequest extends Request = Request>(entitlementKey: Schema.EntitlementType, resourceIdExtractor: (request: TRequest) => string | Promise<string>) => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
|
|
4
|
+
//# sourceMappingURL=entitlement.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlement.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/entitlement.decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAMlC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AA4E5C,eAAO,MAAM,WAAW,GAAI,QAAQ,SAAS,OAAO,GAAG,OAAO,EAC5D,gBAAgB,MAAM,CAAC,eAAe,EACtC,qBAAqB,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,gJAMrE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Entitlement = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const entitlement_guard_1 = require("../guards/entitlement.guard");
|
|
6
|
+
const Entitlement = (entitlementKey, resourceIdExtractor) => {
|
|
7
|
+
return (0, common_1.applyDecorators)((0, entitlement_guard_1.SetEntitlementMetadata)(entitlementKey, resourceIdExtractor), (0, common_1.UseGuards)(entitlement_guard_1.EntitlementGuard));
|
|
8
|
+
};
|
|
9
|
+
exports.Entitlement = Entitlement;
|
|
10
|
+
//# sourceMappingURL=entitlement.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlement.decorator.js","sourceRoot":"","sources":["../../src/decorators/entitlement.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA4D;AAE5D,mEAIqC;AA6E9B,MAAM,WAAW,GAAG,CACzB,cAAsC,EACtC,mBAAoE,EACpE,EAAE;IACF,OAAO,IAAA,wBAAe,EACpB,IAAA,0CAAsB,EAAW,cAAc,EAAE,mBAAmB,CAAC,EACrE,IAAA,kBAAS,EAAC,oCAAgB,CAAC,CAC5B,CAAC;AACJ,CAAC,CAAC;AARW,QAAA,WAAW,eAQtB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { BlimuRuntime, Schema } from '@blimu/runtime-sdk';
|
|
4
|
+
import type { BlimuConfig } from '../config/blimu.config';
|
|
5
|
+
import { Request } from 'express';
|
|
6
|
+
export declare const ENTITLEMENT_KEY = "entitlement";
|
|
7
|
+
export declare const ENTITLEMENT_METADATA_KEY: unique symbol;
|
|
8
|
+
export interface EntitlementMetadata<TRequest extends Request = Request> {
|
|
9
|
+
entitlementKey: Schema.EntitlementType;
|
|
10
|
+
resourceIdExtractor: (request: TRequest) => string | Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
export declare const SetEntitlementMetadata: <TRequest extends Request = Request>(entitlementKey: string, resourceIdExtractor: (request: TRequest) => string | Promise<string>) => import("@nestjs/common").CustomDecorator<typeof ENTITLEMENT_METADATA_KEY>;
|
|
13
|
+
export declare class EntitlementGuard<TRequest extends Request = Request> implements CanActivate {
|
|
14
|
+
private readonly reflector;
|
|
15
|
+
private readonly config;
|
|
16
|
+
private readonly runtime;
|
|
17
|
+
constructor(reflector: Reflector, config: BlimuConfig<TRequest>, runtime: BlimuRuntime);
|
|
18
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=entitlement.guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlement.guard.d.ts","sourceRoot":"","sources":["../../src/guards/entitlement.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,gBAAgB,EAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,wBAAwB,eAAwB,CAAC;AAK9D,MAAM,WAAW,mBAAmB,CAAC,QAAQ,SAAS,OAAO,GAAG,OAAO;IACrE,cAAc,EAAE,MAAM,CAAC,eAAe,CAAC;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE;AAMD,eAAO,MAAM,sBAAsB,GAAI,QAAQ,SAAS,OAAO,GAAG,OAAO,EACvE,gBAAgB,MAAM,EACtB,qBAAqB,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,8EAKjC,CAAC;AAWtC,qBACa,gBAAgB,CAAC,QAAQ,SAAS,OAAO,GAAG,OAAO,CAAE,YAAW,WAAW;IAEpF,OAAO,CAAC,QAAQ,CAAC,SAAS;IAE1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAJP,SAAS,EAAE,SAAS,EAEpB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAE7B,OAAO,EAAE,YAAY;IAGlC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CAwD/D"}
|